/* 编译 .zi 时区数据为 TZif 二进制文件。 */

/*
 * 本文件属于公有领域，经过亚瑟·大卫·奥尔森于
 * 2006-07-17 的说明。
 *
 * 识别
 *	  src/timezone/zic.c
 */

#include "postgres_fe.h"

#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>

#include "pg_getopt.h"

#include "private.h"
#include "tzfile.h"

#define	ZIC_VERSION_PRE_2013 '2'
#define	ZIC_VERSION	'3'

typedef int64 zic_t;
#define ZIC_MIN PG_INT64_MIN
#define ZIC_MAX PG_INT64_MAX

#ifndef ZIC_MAX_ABBR_LEN_WO_WARN
#define ZIC_MAX_ABBR_LEN_WO_WARN	6
#endif							/* !未定义 ZIC_MAX_ABBR_LEN_WO_WARN */

#ifndef WIN32
#ifdef S_IRUSR
#define MKDIR_UMASK (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
#else
#define MKDIR_UMASK 0755
#endif
#endif
/* 移植到本地 MS-Windows 和古老的 UNIX。 */
#if !defined S_ISDIR && defined S_IFDIR && defined S_IFMT
#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
#endif

/* ptrdiff_t 的最大值，用于 C99 之前的平台。 */
#ifndef PTRDIFF_MAX
static ptrdiff_t const PTRDIFF_MAX = MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t));
#endif

/*
 * 行号的类型。在 Postgres 中，使用 %d 格式化它们；上游
 * 使用 PRIdMAX，但我们更倾向于不依赖它，尤其是因为它
 * 会导致需要翻译的平台相关字符串。
 */
typedef int lineno_t;

struct rule
{
	const char *r_filename;
	lineno_t	r_linenum;
	const char *r_name;

	zic_t		r_loyear;		/* 例如，1986 */
	zic_t		r_hiyear;		/* 例如，1986 */
	bool		r_lowasnum;
	bool		r_hiwasnum;

	int			r_month;		/* 0..11 */

	int			r_dycode;		/* 见下文 */
	int			r_dayofmonth;
	int			r_wday;

	zic_t		r_tod;			/* 从午夜开始的时间 */
	bool		r_todisstd;		/* r_tod 是标准时间吗？ */
	bool		r_todisut;		/* r_tod 是 UTC 吗？ */
	bool		r_isdst;		/* 这是夏令时吗？ */
	zic_t		r_save;			/* 相对于标准时间的偏移量 */
	const char *r_abbrvar;		/* 缩写的可变部分 */

	bool		r_todo;			/* 要执行的规则（用于 outzone） */
	zic_t		r_temp;			/* 用于 outzone */
};

/*
 *	r_dycode		r_dayofmonth	r_wday
 */

#define DC_DOM		0	/* 1..31 */ /* 未使用 */
#define DC_DOWGEQ	1	/* 1..31 */ /* 0..6（周日..周六） */
#define DC_DOWLEQ	2	/* 1..31 */ /* 0..6（周日..周六） */

struct zone
{
	const char *z_filename;
	lineno_t	z_linenum;

	const char *z_name;
	zic_t		z_stdoff;
	char	   *z_rule;
	const char *z_format;
	char		z_format_specifier;

	bool		z_isdst;
	zic_t		z_save;

	struct rule *z_rules;
	ptrdiff_t	z_nrules;

	struct rule z_untilrule;
	zic_t		z_untiltime;
};

extern int	link(const char *fc_target, const char *fc_linkname);
#ifndef AT_SYMLINK_FOLLOW
#define linkat(targetdir, target, linknamedir, linkname, flag) \
	(itssymlink(target) ? (errno = ENOTSUP, -1) : link(target, linkname))
#endif

static void fc_memory_exhausted(const char *fc_msg) pg_attribute_noreturn();
static void fc_verror(const char *fc_string, va_list fc_args) pg_attribute_printf(1, 0);
static void fc_error(const char *fc_string,...) pg_attribute_printf(1, 2);
static void fc_warning(const char *fc_string,...) pg_attribute_printf(1, 2);
static void fc_usage(FILE *fc_stream, int fc_status) pg_attribute_noreturn();
static void fc_addtt(zic_t fc_starttime, int fc_type);
static int	fc_addtype(zic_t, char const *, bool, bool, bool);
static void fc_leapadd(zic_t, int, int);
static void fc_adjleap(void);
static void fc_associate(void);
static void fc_dolink(const char *, const char *, bool);
static char **fc_getfields(char *fc_buf);
static zic_t fc_gethms(const char *fc_string, const char *fc_errstring);
static zic_t fc_getsave(char *, bool *);
static void fc_inexpires(char **, int);
static void fc_infile(const char *fc_filename);
static void fc_inleap(char **fc_fields, int fc_nfields);
static void fc_inlink(char **fc_fields, int fc_nfields);
static void fc_inrule(char **fc_fields, int fc_nfields);
static bool fc_inzcont(char **fc_fields, int fc_nfields);
static bool fc_inzone(char **fc_fields, int fc_nfields);
static bool fc_inzsub(char **, int, bool);
static bool fc_itsdir(char const *);
static bool fc_itssymlink(char const *);
static bool fc_is_alpha(char fc_a);
static char fc_lowerit(char);
static void fc_mkdirs(char const *, bool);
static void fc_newabbr(const char *fc_abbr);
static zic_t fc_oadd(zic_t fc_t1, zic_t fc_t2);
static void fc_outzone(const struct zone *fc_zp, ptrdiff_t fc_ntzones);
static zic_t fc_rpytime(const struct rule *fc_rp, zic_t fc_wantedy);
static void fc_rulesub(struct rule *fc_rp,
					const char *fc_loyearp, const char *fc_hiyearp,
					const char *fc_typep, const char *fc_monthp,
					const char *fc_dayp, const char *fc_timep);
static zic_t fc_tadd(zic_t fc_t1, zic_t fc_t2);

/* %z 可以扩展的长度限制。 */
enum
{
PERCENT_Z_LEN_BOUND = sizeof "+995959" - 1};

/* 如果为真，解决 Qt 5.6.1 及更早版本中的一个缺陷，该缺陷错误处理
   包含 '<' 的 POSIX-TZ 风格字符串的 TZif 文件；参见
   QTBUG-53071 <https://bugreports.qt.io/browse/QTBUG-53071>。当 Qt 5.6.1 及更早版本
   过时时，比如在 2021 年，这个解决方法就不再需要。 */
#ifndef WORK_AROUND_QTBUG_53071
enum
{
WORK_AROUND_QTBUG_53071 = true};
#endif

static int	charcnt;
static bool errors;
static bool warnings;
static const char *filename;
static int	leapcnt;
static bool leapseen;
static zic_t leapminyear;
static zic_t leapmaxyear;
static lineno_t linenum;
static int	max_abbrvar_len = PERCENT_Z_LEN_BOUND;
static int	max_format_len;
static zic_t max_year;
static zic_t min_year;
static bool noise;
static bool print_abbrevs;
static zic_t print_cutoff;
static const char *rfilename;
static lineno_t rlinenum;
static const char *progname;
static ptrdiff_t timecnt;
static ptrdiff_t timecnt_alloc;
static int	typecnt;

/*
 * 行代码。
 */

#define LC_RULE		0
#define LC_ZONE		1
#define LC_LINK		2
#define LC_LEAP		3
#define LC_EXPIRES	4

/*
 * 区域行中的字段是什么。
 */

#define ZF_NAME		1
#define ZF_STDOFF	2
#define ZF_RULE		3
#define ZF_FORMAT	4
#define ZF_TILYEAR	5
#define ZF_TILMONTH	6
#define ZF_TILDAY	7
#define ZF_TILTIME	8
#define ZONE_MINFIELDS	5
#define ZONE_MAXFIELDS	9

/*
 * 区域续行中的字段是什么。
 */

#define ZFC_STDOFF	0
#define ZFC_RULE	1
#define ZFC_FORMAT	2
#define ZFC_TILYEAR	3
#define ZFC_TILMONTH	4
#define ZFC_TILDAY	5
#define ZFC_TILTIME	6
#define ZONEC_MINFIELDS	3
#define ZONEC_MAXFIELDS	7

/*
 * 规则行中的文件是什么。
 */

#define RF_NAME		1
#define RF_LOYEAR	2
#define RF_HIYEAR	3
#define RF_COMMAND	4
#define RF_MONTH	5
#define RF_DAY		6
#define RF_TOD		7
#define RF_SAVE		8
#define RF_ABBRVAR	9
#define RULE_FIELDS	10

/*
 * 链接行中的字段是什么。
 */

#define LF_TARGET	1
#define LF_LINKNAME	2
#define LINK_FIELDS	3

/*
 * 闰年行中的字段是什么。
 */

#define LP_YEAR		1
#define LP_MONTH	2
#define LP_DAY		3
#define LP_TIME		4
#define LP_CORR		5
#define LP_ROLL		6
#define LEAP_FIELDS	7

/* 过期行与闰年行类似，但没有 CORR 和 ROLL 字段。 */
#define EXPIRES_FIELDS	5

/*
 * 年的同义词。
 */

#define YR_MINIMUM	0
#define YR_MAXIMUM	1
#define YR_ONLY		2

static struct rule *rules;
static ptrdiff_t nrules;		/* 规则数量 */
static ptrdiff_t nrules_alloc;

static struct zone *zones;
static ptrdiff_t nzones;		/* 时区数量 */
static ptrdiff_t nzones_alloc;

struct link
{
	const char *l_filename;
	lineno_t	l_linenum;
	const char *l_target;
	const char *l_linkname;
};

static struct link *links;
static ptrdiff_t nlinks;
static ptrdiff_t nlinks_alloc;

struct lookup
{
	const char *l_word;
	const int	l_value;
};

static struct lookup const *fc_byword(const char *fc_string,
								   const struct lookup *fc_lp);

static struct lookup const zi_line_codes[] = {
	{"Rule", LC_RULE},
	{"Zone", LC_ZONE},
	{"Link", LC_LINK},
	{NULL, 0}
};
static struct lookup const leap_line_codes[] = {
	{"Leap", LC_LEAP},
	{"Expires", LC_EXPIRES},
	{NULL, 0}
};

static struct lookup const mon_names[] = {
	{"January", TM_JANUARY},
	{"February", TM_FEBRUARY},
	{"March", TM_MARCH},
	{"April", TM_APRIL},
	{"May", TM_MAY},
	{"June", TM_JUNE},
	{"July", TM_JULY},
	{"August", TM_AUGUST},
	{"September", TM_SEPTEMBER},
	{"October", TM_OCTOBER},
	{"November", TM_NOVEMBER},
	{"December", TM_DECEMBER},
	{NULL, 0}
};

static struct lookup const wday_names[] = {
	{"Sunday", TM_SUNDAY},
	{"Monday", TM_MONDAY},
	{"Tuesday", TM_TUESDAY},
	{"Wednesday", TM_WEDNESDAY},
	{"Thursday", TM_THURSDAY},
	{"Friday", TM_FRIDAY},
	{"Saturday", TM_SATURDAY},
	{NULL, 0}
};

static struct lookup const lasts[] = {
	{"last-Sunday", TM_SUNDAY},
	{"last-Monday", TM_MONDAY},
	{"last-Tuesday", TM_TUESDAY},
	{"last-Wednesday", TM_WEDNESDAY},
	{"last-Thursday", TM_THURSDAY},
	{"last-Friday", TM_FRIDAY},
	{"last-Saturday", TM_SATURDAY},
	{NULL, 0}
};

static struct lookup const begin_years[] = {
	{"minimum", YR_MINIMUM},
	{"maximum", YR_MAXIMUM},
	{NULL, 0}
};

static struct lookup const end_years[] = {
	{"minimum", YR_MINIMUM},
	{"maximum", YR_MAXIMUM},
	{"only", YR_ONLY},
	{NULL, 0}
};

static struct lookup const leap_types[] = {
	{"Rolling", true},
	{"Stationary", false},
	{NULL, 0}
};

static const int len_months[2][MONSPERYEAR] = {
	{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
	{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};

static const int len_years[2] = {
	DAYSPERNYEAR, DAYSPERLYEAR
};

static struct attype
{
	zic_t		at;
	bool		dontmerge;
	unsigned char type;
}		   *attypes;
static zic_t utoffs[TZ_MAX_TYPES];
static char isdsts[TZ_MAX_TYPES];
static unsigned char desigidx[TZ_MAX_TYPES];
static bool ttisstds[TZ_MAX_TYPES];
static bool ttisuts[TZ_MAX_TYPES];
static char chars[TZ_MAX_CHARS];
static zic_t trans[TZ_MAX_LEAPS];
static zic_t corr[TZ_MAX_LEAPS];
static char roll[TZ_MAX_LEAPS];

/*
 * 内存分配。
 */

static void fc_memory_exhausted(const char *fc_msg)
{
	fprintf(stderr, _("%s: Memory exhausted: %s\n"), progname, fc_msg);
	exit(EXIT_FAILURE);
}

static size_t fc_size_product(size_t fc_nitems, size_t fc_itemsize)
{
	if (SIZE_MAX / fc_itemsize < fc_nitems)
		fc_memory_exhausted(_("size overflow"));
	return fc_nitems * fc_itemsize;
}

static void * fc_memcheck(void *fc_ptr)
{
	if (fc_ptr == NULL)
		fc_memory_exhausted(strerror(errno));
	return fc_ptr;
}

static void * fc_emalloc(size_t fc_size)
{
	return fc_memcheck(malloc(fc_size));
}

static void * fc_erealloc(void *fc_ptr, size_t fc_size)
{
	return fc_memcheck(realloc(fc_ptr, fc_size));
}

static char * fc_ecpyalloc(char const *fc_str)
{
	return fc_memcheck(strdup(fc_str));
}

static void * fc_growalloc(void *fc_ptr, size_t fc_itemsize, ptrdiff_t fc_nitems, ptrdiff_t *fc_nitems_alloc)
{
	if (fc_nitems < *fc_nitems_alloc)
		return fc_ptr;
	else
	{
		ptrdiff_t	fc_nitems_max = PTRDIFF_MAX - WORK_AROUND_QTBUG_53071;
		ptrdiff_t	fc_amax = fc_nitems_max < SIZE_MAX ? fc_nitems_max : SIZE_MAX;

		if ((fc_amax - 1) / 3 * 2 < *fc_nitems_alloc)
			fc_memory_exhausted(_("integer overflow"));
		*fc_nitems_alloc += (*fc_nitems_alloc >> 1) + 1;
		return fc_erealloc(fc_ptr, fc_size_product(*fc_nitems_alloc, fc_itemsize));
	}
}

/*
 * 错误处理。
 */

static void fc_eats(char const *fc_name, lineno_t fc_num, char const *fc_rname, lineno_t fc_rnum)
{
	filename = fc_name;
	linenum = fc_num;
	rfilename = fc_rname;
	rlinenum = fc_rnum;
}

static void fc_eat(char const *fc_name, lineno_t fc_num)
{
	fc_eats(fc_name, fc_num, NULL, -1);
}

static void fc_verror(const char *fc_string, va_list fc_args)
{
	/*
	 * 匹配 "cc" 的格式，允许 sh 用户使用 zic ... 2>&1 | error -t
	 * "*" -v 在 BSD 系统上。
	 */
	if (filename)
		fprintf(stderr, _("\"%s\", line %d: "), filename, linenum);
	vfprintf(stderr, fc_string, fc_args);
	if (rfilename != NULL)
		fprintf(stderr, _(" (rule from \"%s\", line %d)"),
				rfilename, rlinenum);
	fprintf(stderr, "\n");
}

static void fc_error(const char *fc_string,...)
{
	va_list		fc_args;

	va_start(fc_args, fc_string);
	fc_verror(fc_string, fc_args);
	va_end(fc_args);
	errors = true;
}

static void fc_warning(const char *fc_string,...)
{
	va_list		fc_args;

	fprintf(stderr, _("warning: "));
	va_start(fc_args, fc_string);
	fc_verror(fc_string, fc_args);
	va_end(fc_args);
	warnings = true;
}

static void fc_close_file(FILE *fc_stream, char const *fc_dir, char const *fc_name)
{
	char const *fc_e = (ferror(fc_stream) ? _("I/O error")
					 : fclose(fc_stream) != 0 ? strerror(errno) : NULL);

	if (fc_e)
	{
		fprintf(stderr, "%s: %s%s%s%s%s\n", progname,
				fc_dir ? fc_dir : "", fc_dir ? "/" : "",
				fc_name ? fc_name : "", fc_name ? ": " : "",
				fc_e);
		exit(EXIT_FAILURE);
	}
}

static void fc_usage(FILE *fc_stream, int fc_status)
{
	fprintf(fc_stream,
			_("%s: usage is %s [ --version ] [ --help ] [ -v ] [ -P ] \\\n"
			  "\t[ -b {slim|fat} ] [ -d directory ] [ -l localtime ]"
			  " [ -L leapseconds ] \\\n"
			  "\t[ -p posixrules ] [ -r '[@lo][/@hi]' ] [ -t localtime-link ] \\\n"
			  "\t[ filename ... ]\n\n"
			  "Report bugs to %s.\n"),
			progname, progname, PACKAGE_BUGREPORT);
	if (fc_status == EXIT_SUCCESS)
		fc_close_file(fc_stream, NULL, NULL);
	exit(fc_status);
}

/* 将工作目录更改为 DIR，可能创建 DIR 及其
   祖先。完成后，所有文件都将通过相对于 DIR 的名称访问。 */
static void fc_change_directory(char const *fc_dir)
{
	if (chdir(fc_dir) != 0)
	{
		int			fc_chdir_errno = errno;

		if (fc_chdir_errno == ENOENT)
		{
			fc_mkdirs(fc_dir, false);
			fc_chdir_errno = chdir(fc_dir) == 0 ? 0 : errno;
		}
		if (fc_chdir_errno != 0)
		{
			fprintf(stderr, _("%s: Can't chdir to %s: %s\n"),
					progname, fc_dir, strerror(fc_chdir_errno));
			exit(EXIT_FAILURE);
		}
	}
}

#define TIME_T_BITS_IN_FILE 64

/* TZif 文件中可表示的最小值和最大值。 */
static zic_t const min_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE);
static zic_t const max_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE);

/* -r 选项指定的最小值和最大值减一的值。
   默认值为 MIN_TIME 和 MAX_TIME。 */
static zic_t lo_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE);
static zic_t hi_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE);

/* 过期行指定的时间，如果没有此行则为负。 */
static zic_t leapexpires = -1;

/* #expires 注释指定的时间，如果没有此行则为负。 */
static zic_t comment_leapexpires = -1;

/* 将输出的时间范围设置为 TIMERANGE。
   成功时返回真。 */
static bool fc_timerange_option(char *fc_timerange)
{
	int64		fc_lo = min_time,
				fc_hi = max_time;
	char	   *fc_lo_end = fc_timerange,
			   *fc_hi_end;

	if (*fc_timerange == '@')
	{
		errno = 0;
		fc_lo = strtoimax(fc_timerange + 1, &fc_lo_end, 10);
		if (fc_lo_end == fc_timerange + 1 || (fc_lo == PG_INT64_MAX && errno == ERANGE))
			return false;
	}
	fc_hi_end = fc_lo_end;
	if (fc_lo_end[0] == '/' && fc_lo_end[1] == '@')
	{
		errno = 0;
		fc_hi = strtoimax(fc_lo_end + 2, &fc_hi_end, 10);
		if (fc_hi_end == fc_lo_end + 2 || fc_hi == PG_INT64_MIN)
			return false;
		fc_hi -= !(fc_hi == PG_INT64_MAX && errno == ERANGE);
	}
	if (*fc_hi_end || fc_hi < fc_lo || max_time < fc_lo || fc_hi < min_time)
		return false;
	lo_time = fc_lo < min_time ? min_time : fc_lo;
	hi_time = max_time < fc_hi ? max_time : fc_hi;
	return true;
}

static const char *psxrules;
static const char *lcltime;
static const char *directory;
static const char *leapsec;
static const char *tzdefault;

/* 如果 TZif 输出文件应为紧凑型，则为 -1；如果为默认值，则为 0；
   如果输出应为因向后兼容而变大的，则为 1。ZIC_BLOAT_DEFAULT
   确定默认值。 */
static int	bloat;

static bool fc_want_bloat(void)
{
	return 0 <= bloat;
}

#ifndef ZIC_BLOAT_DEFAULT
#define ZIC_BLOAT_DEFAULT "slim"
#endif

int main(int fc_argc, char **fc_argv)
{
	int			fc_c,
				fc_k;
	ptrdiff_t	fc_i,
				fc_j;
	bool		fc_timerange_given = false;

#ifndef WIN32
	umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH));
#endif
	progname = fc_argv[0];
	if (TYPE_BIT(zic_t) < 64)
	{
		fprintf(stderr, "%s: %s\n", progname,
				_("wild compilation-time specification of zic_t"));
		return EXIT_FAILURE;
	}
	for (fc_k = 1; fc_k < fc_argc; fc_k++)
		if (strcmp(fc_argv[fc_k], "--version") == 0)
		{
			printf("zic %s\n", PG_VERSION);
			fc_close_file(stdout, NULL, NULL);
			return EXIT_SUCCESS;
		}
		else if (strcmp(fc_argv[fc_k], "--help") == 0)
		{
			fc_usage(stdout, EXIT_SUCCESS);
		}
	while ((fc_c = getopt(fc_argc, fc_argv, "b:d:l:L:p:Pr:st:vy:")) != EOF && fc_c != -1)
		switch (fc_c)
		{
			default:
				fc_usage(stderr, EXIT_FAILURE);
			case 'b':
				if (strcmp(optarg, "slim") == 0)
				{
					if (0 < bloat)
						fc_error(_("incompatible -b options"));
					bloat = -1;
				}
				else if (strcmp(optarg, "fat") == 0)
				{
					if (bloat < 0)
						fc_error(_("incompatible -b options"));
					bloat = 1;
				}
				else
					fc_error(_("invalid option: -b '%s'"), optarg);
				break;
			case 'd':
				if (directory == NULL)
					directory = strdup(optarg);
				else
				{
					fprintf(stderr,
							_("%s: More than one -d option specified\n"),
							progname);
					return EXIT_FAILURE;
				}
				break;
			case 'l':
				if (lcltime == NULL)
					lcltime = strdup(optarg);
				else
				{
					fprintf(stderr,
							_("%s: More than one -l option specified\n"),
							progname);
					return EXIT_FAILURE;
				}
				break;
			case 'p':
				if (psxrules == NULL)
					psxrules = strdup(optarg);
				else
				{
					fprintf(stderr,
							_("%s: More than one -p option specified\n"),
							progname);
					return EXIT_FAILURE;
				}
				break;
			case 't':
				if (tzdefault != NULL)
				{
					fprintf(stderr,
							_("%s: More than one -t option"
							  " specified\n"),
							progname);
					return EXIT_FAILURE;
				}
				tzdefault = optarg;
				break;
			case 'y':
				fc_warning(_("-y ignored"));
				break;
			case 'L':
				if (leapsec == NULL)
					leapsec = strdup(optarg);
				else
				{
					fprintf(stderr,
							_("%s: More than one -L option specified\n"),
							progname);
					return EXIT_FAILURE;
				}
				break;
			case 'v':
				noise = true;
				break;
			case 'P':
				print_abbrevs = true;
				print_cutoff = time(NULL);
				break;
			case 'r':
				if (fc_timerange_given)
				{
					fprintf(stderr,
							_("%s: More than one -r option specified\n"),
							progname);
					return EXIT_FAILURE;
				}
				if (!fc_timerange_option(optarg))
				{
					fprintf(stderr,
							_("%s: invalid time range: %s\n"),
							progname, optarg);
					return EXIT_FAILURE;
				}
				fc_timerange_given = true;
				break;
			case 's':
				fc_warning(_("-s ignored"));
				break;
		}
	if (optind == fc_argc - 1 && strcmp(fc_argv[optind], "=") == 0)
		fc_usage(stderr, EXIT_FAILURE);	/* 根据请求显示使用信息 */
	if (bloat == 0)
	{
		static char const fc_bloat_default[] = ZIC_BLOAT_DEFAULT;

		if (strcmp(fc_bloat_default, "slim") == 0)
			bloat = -1;
		else if (strcmp(fc_bloat_default, "fat") == 0)
			bloat = 1;
		else
			abort();			/* 配置错误。  */
	}
	if (directory == NULL)
		directory = "data";
	if (tzdefault == NULL)
		tzdefault = TZDEFAULT;

	if (optind < fc_argc && leapsec != NULL)
	{
		fc_infile(leapsec);
		fc_adjleap();
	}

	for (fc_k = optind; fc_k < fc_argc; fc_k++)
		fc_infile(fc_argv[fc_k]);
	if (errors)
		return EXIT_FAILURE;
	fc_associate();
	fc_change_directory(directory);
	for (fc_i = 0; fc_i < nzones; fc_i = fc_j)
	{
		/*
		 * 查找下一个非续区条目。
		 */
		for (fc_j = fc_i + 1; fc_j < nzones && zones[fc_j].z_name == NULL; ++fc_j)
			continue;
		fc_outzone(&zones[fc_i], fc_j - fc_i);
	}

	/*
	 * 创建链接。
	 */
	for (fc_i = 0; fc_i < nlinks; ++fc_i)
	{
		fc_eat(links[fc_i].l_filename, links[fc_i].l_linenum);
		fc_dolink(links[fc_i].l_target, links[fc_i].l_linkname, false);
		if (noise)
			for (fc_j = 0; fc_j < nlinks; ++fc_j)
				if (strcmp(links[fc_i].l_linkname,
						   links[fc_j].l_target) == 0)
					fc_warning(_("link to link"));
	}
	if (lcltime != NULL)
	{
		fc_eat(_("command line"), 1);
		fc_dolink(lcltime, tzdefault, true);
	}
	if (psxrules != NULL)
	{
		fc_eat(_("command line"), 1);
		fc_dolink(psxrules, TZDEFRULES, true);
	}
	if (warnings && (ferror(stderr) || fclose(stderr) != 0))
		return EXIT_FAILURE;
	return errors ? EXIT_FAILURE : EXIT_SUCCESS;
}

static bool fc_componentcheck(char const *fc_name, char const *fc_component,
			   char const *fc_component_end)
{
	enum
	{
	component_len_max = 14};
	ptrdiff_t	fc_component_len = fc_component_end - fc_component;

	if (fc_component_len == 0)
	{
		if (!*fc_name)
			fc_error(_("empty file name"));
		else
			fc_error(_(fc_component == fc_name
					? "file name '%s' begins with '/'"
					: *fc_component_end
					? "file name '%s' contains '//'"
					: "file name '%s' ends with '/'"),
				  fc_name);
		return false;
	}
	if (0 < fc_component_len && fc_component_len <= 2
		&& fc_component[0] == '.' && fc_component_end[-1] == '.')
	{
		int			fc_len = fc_component_len;

		fc_error(_("file name '%s' contains '%.*s' component"),
			  fc_name, fc_len, fc_component);
		return false;
	}
	if (noise)
	{
		if (0 < fc_component_len && fc_component[0] == '-')
			fc_warning(_("file name '%s' component contains leading '-'"),
					fc_name);
		if (component_len_max < fc_component_len)
			fc_warning(_("file name '%s' contains overlength component"
					  " '%.*s...'"),
					fc_name, component_len_max, fc_component);
	}
	return true;
}

static bool fc_namecheck(const char *fc_name)
{
	char const *fc_cp;

	/* 可移植文件名中的良性字符。  */
	static char const fc_benign[] =
	"-/_"
	"abcdefghijklmnopqrstuvwxyz"
	"ABCDEFGHIJKLMNOPQRSTUVWXYZ";

	/*
	 * 在POSIX可移植字符集中的非控制字符，排除良性字符。
	 */
	static char const fc_printable_and_not_benign[] =
	" !\"#$%&'()*+,.0123456789:;<=>?@[\\]^`{|}~";

	char const *fc_component = fc_name;

	for (fc_cp = fc_name; *fc_cp; fc_cp++)
	{
		unsigned char fc_c = *fc_cp;

		if (noise && !strchr(fc_benign, fc_c))
		{
			fc_warning((strchr(fc_printable_and_not_benign, fc_c)
					 ? _("file name '%s' contains byte '%c'")
					 : _("file name '%s' contains byte '\\%o'")),
					fc_name, fc_c);
		}
		if (fc_c == '/')
		{
			if (!fc_componentcheck(fc_name, fc_component, fc_cp))
				return false;
			fc_component = fc_cp + 1;
		}
	}
	return fc_componentcheck(fc_name, fc_component, fc_cp);
}

/*
 * 创建适合从FROM到TO链接的符号链接内容，作为一个新分配的字符串。  FROM应该是相对文件名，并且相对于全局变量DIRECTORY。  TO可以是相对的或绝对的。
 */
#ifdef HAVE_SYMLINK
static char * fc_relname(char const *fc_target, char const *fc_linkname)
{
	size_t		fc_i,
				fc_taillen,
				fc_dotdotetcsize;
	size_t		fc_dir_len = 0,
				fc_dotdots = 0,
				fc_linksize = SIZE_MAX;
	char const *fc_f = fc_target;
	char	   *fc_result = NULL;

	if (*fc_linkname == '/')
	{
		/* 也将F变为绝对路径。  */
		size_t		fc_len = strlen(directory);
		bool		fc_needslash = fc_len && directory[fc_len - 1] != '/';

		fc_linksize = fc_len + fc_needslash + strlen(fc_target) + 1;
		fc_f = fc_result = fc_emalloc(fc_linksize);
		strcpy(fc_result, directory);
		fc_result[fc_len] = '/';
		strcpy(fc_result + fc_len + fc_needslash, fc_target);
	}
	for (fc_i = 0; fc_f[fc_i] && fc_f[fc_i] == fc_linkname[fc_i]; fc_i++)
		if (fc_f[fc_i] == '/')
			fc_dir_len = fc_i + 1;
	for (; fc_linkname[fc_i]; fc_i++)
		fc_dotdots += fc_linkname[fc_i] == '/' && fc_linkname[fc_i - 1] != '/';
	fc_taillen = strlen(fc_f + fc_dir_len);
	fc_dotdotetcsize = 3 * fc_dotdots + fc_taillen + 1;
	if (fc_dotdotetcsize <= fc_linksize)
	{
		if (!fc_result)
			fc_result = fc_emalloc(fc_dotdotetcsize);
		for (fc_i = 0; fc_i < fc_dotdots; fc_i++)
			memcpy(fc_result + 3 * fc_i, "../", 3);
		memmove(fc_result + 3 * fc_dotdots, fc_f + fc_dir_len, fc_taillen + 1);
	}
	return fc_result;
}
#endif							/* HAVE_SYMLINK */

/* 将FROM的硬链接到TO，遵循任何符号链接。
   如果成功则返回0，否则返回错误号。  */
static int fc_hardlinkerr(char const *fc_target, char const *fc_linkname)
{
	int			fc_r = linkat(AT_FDCWD, fc_target, AT_FDCWD, fc_linkname, AT_SYMLINK_FOLLOW);

	return fc_r == 0 ? 0 : errno;
}

static void fc_dolink(char const *fc_target, char const *fc_linkname, bool fc_staysymlink)
{
	bool		fc_remove_only = strcmp(fc_target, "-") == 0;
	bool		fc_linkdirs_made = false;
	int			fc_link_errno;

	/*
	 * 在这里我们要小心，因为有相当大的机会是root在运行我们。
	 */
	if (!fc_remove_only && fc_itsdir(fc_target))
	{
		fprintf(stderr, _("%s: linking target %s/%s failed: %s\n"),
				progname, directory, fc_target, strerror(EPERM));
		exit(EXIT_FAILURE);
	}
	if (fc_staysymlink)
		fc_staysymlink = fc_itssymlink(fc_linkname);
	if (remove(fc_linkname) == 0)
		fc_linkdirs_made = true;
	else if (errno != ENOENT)
	{
		char const *fc_e = strerror(errno);

		fprintf(stderr, _("%s: Can't remove %s/%s: %s\n"),
				progname, directory, fc_linkname, fc_e);
		exit(EXIT_FAILURE);
	}
	if (fc_remove_only)
		return;
	fc_link_errno = fc_staysymlink ? ENOTSUP : fc_hardlinkerr(fc_target, fc_linkname);
	if (fc_link_errno == ENOENT && !fc_linkdirs_made)
	{
		fc_mkdirs(fc_linkname, true);
		fc_linkdirs_made = true;
		fc_link_errno = fc_hardlinkerr(fc_target, fc_linkname);
	}
	if (fc_link_errno != 0)
	{
#ifdef HAVE_SYMLINK
		bool		fc_absolute = *fc_target == '/';
		char	   *fc_linkalloc = fc_absolute ? NULL : fc_relname(fc_target, fc_linkname);
		char const *fc_contents = fc_absolute ? fc_target : fc_linkalloc;
		int			fc_symlink_errno = symlink(fc_contents, fc_linkname) == 0 ? 0 : errno;

		if (!fc_linkdirs_made
			&& (fc_symlink_errno == ENOENT || fc_symlink_errno == ENOTSUP))
		{
			fc_mkdirs(fc_linkname, true);
			if (fc_symlink_errno == ENOENT)
				fc_symlink_errno = symlink(fc_contents, fc_linkname) == 0 ? 0 : errno;
		}
		free(fc_linkalloc);
		if (fc_symlink_errno == 0)
		{
			if (fc_link_errno != ENOTSUP)
				fc_warning(_("symbolic link used because hard link failed: %s"),
						strerror(fc_link_errno));
		}
		else
#endif							/* HAVE_SYMLINK */
		{
			FILE	   *fc_fp,
					   *fc_tp;
			int			fc_c;

			fc_fp = fopen(fc_target, "rb");
			if (!fc_fp)
			{
				char const *fc_e = strerror(errno);

				fprintf(stderr, _("%s: Can't read %s/%s: %s\n"),
						progname, directory, fc_target, fc_e);
				exit(EXIT_FAILURE);
			}
			fc_tp = fopen(fc_linkname, "wb");
			if (!fc_tp)
			{
				char const *fc_e = strerror(errno);

				fprintf(stderr, _("%s: Can't create %s/%s: %s\n"),
						progname, directory, fc_linkname, fc_e);
				exit(EXIT_FAILURE);
			}
			while ((fc_c = getc(fc_fp)) != EOF)
				putc(fc_c, fc_tp);
			fc_close_file(fc_fp, directory, fc_target);
			fc_close_file(fc_tp, directory, fc_linkname);
			if (fc_link_errno != ENOTSUP)
				fc_warning(_("copy used because hard link failed: %s"),
						strerror(fc_link_errno));
#ifdef HAVE_SYMLINK
			else if (fc_symlink_errno != ENOTSUP)
				fc_warning(_("copy used because symbolic link failed: %s"),
						strerror(fc_symlink_errno));
#endif
		}
	}
}

/* 如果NAME是目录，则返回true。  */
static bool fc_itsdir(char const *fc_name)
{
	struct stat fc_st;
	int			fc_res = stat(fc_name, &fc_st);
#ifdef S_ISDIR
	if (fc_res == 0)
		return S_ISDIR(fc_st.st_mode) != 0;
#endif
	if (fc_res == 0 || errno == EOVERFLOW)
	{
		size_t		fc_n = strlen(fc_name);
		char	   *fc_nameslashdot = fc_emalloc(fc_n + 3);
		bool		fc_dir;

		memcpy(fc_nameslashdot, fc_name, fc_n);
		strcpy(&fc_nameslashdot[fc_n], &"/."[!(fc_n && fc_name[fc_n - 1] != '/')]);
		fc_dir = stat(fc_nameslashdot, &fc_st) == 0 || errno == EOVERFLOW;
		free(fc_nameslashdot);
		return fc_dir;
	}
	return false;
}

/* 如果NAME是符号链接，则返回true。  */
static bool fc_itssymlink(char const *fc_name)
{
#ifdef HAVE_SYMLINK
	char		fc_c;

	return 0 <= readlink(fc_name, &fc_c, 1);
#else
	return false;
#endif
}

/*
 * 将规则集与区域关联。
 */

/*
 * 按规则名称排序。
 */

static int fc_rcomp(const void *fc_cp1, const void *fc_cp2)
{
	return strcmp(((const struct rule *) fc_cp1)->r_name,
				  ((const struct rule *) fc_cp2)->r_name);
}

static void fc_associate(void)
{
	struct zone *fc_zp;
	struct rule *fc_rp;
	ptrdiff_t	fc_i,
				fc_j,
				fc_base,
				fc_out;

	if (nrules != 0)
	{
		qsort(rules, nrules, sizeof *rules, fc_rcomp);
		for (fc_i = 0; fc_i < nrules - 1; ++fc_i)
		{
			if (strcmp(rules[fc_i].r_name,
					   rules[fc_i + 1].r_name) != 0)
				continue;
			if (strcmp(rules[fc_i].r_filename,
					   rules[fc_i + 1].r_filename) == 0)
				continue;
			fc_eat(rules[fc_i].r_filename, rules[fc_i].r_linenum);
			fc_warning(_("same rule name in multiple files"));
			fc_eat(rules[fc_i + 1].r_filename, rules[fc_i + 1].r_linenum);
			fc_warning(_("same rule name in multiple files"));
			for (fc_j = fc_i + 2; fc_j < nrules; ++fc_j)
			{
				if (strcmp(rules[fc_i].r_name,
						   rules[fc_j].r_name) != 0)
					break;
				if (strcmp(rules[fc_i].r_filename,
						   rules[fc_j].r_filename) == 0)
					continue;
				if (strcmp(rules[fc_i + 1].r_filename,
						   rules[fc_j].r_filename) == 0)
					continue;
				break;
			}
			fc_i = fc_j - 1;
		}
	}
	for (fc_i = 0; fc_i < nzones; ++fc_i)
	{
		fc_zp = &zones[fc_i];
		fc_zp->z_rules = NULL;
		fc_zp->z_nrules = 0;
	}
	for (fc_base = 0; fc_base < nrules; fc_base = fc_out)
	{
		fc_rp = &rules[fc_base];
		for (fc_out = fc_base + 1; fc_out < nrules; ++fc_out)
			if (strcmp(fc_rp->r_name, rules[fc_out].r_name) != 0)
				break;
		for (fc_i = 0; fc_i < nzones; ++fc_i)
		{
			fc_zp = &zones[fc_i];
			if (strcmp(fc_zp->z_rule, fc_rp->r_name) != 0)
				continue;
			fc_zp->z_rules = fc_rp;
			fc_zp->z_nrules = fc_out - fc_base;
		}
	}
	for (fc_i = 0; fc_i < nzones; ++fc_i)
	{
		fc_zp = &zones[fc_i];
		if (fc_zp->z_nrules == 0)
		{
			/*
			 * 也许我们有本地标准时间偏移。
			 */
			fc_eat(fc_zp->z_filename, fc_zp->z_linenum);
			fc_zp->z_save = fc_getsave(fc_zp->z_rule, &fc_zp->z_isdst);

			/*
			 * 不过请注意，如果没有规则，格式中的'%s'是件坏事。
			 */
			if (fc_zp->z_format_specifier == 's')
				fc_error("%s", _("%s in ruleless zone"));
		}
	}
	if (errors)
		exit(EXIT_FAILURE);
}

static void fc_infile(const char *fc_name)
{
	FILE	   *fc_fp;
	char	  **fc_fields;
	char	   *fc_cp;
	const struct lookup *fc_lp;
	int			fc_nfields;
	bool		fc_wantcont;
	lineno_t	fc_num;
	char		fc_buf[BUFSIZ];

	if (strcmp(fc_name, "-") == 0)
	{
		fc_name = _("standard input");
		fc_fp = stdin;
	}
	else if ((fc_fp = fopen(fc_name, "r")) == NULL)
	{
		const char *fc_e = strerror(errno);

		fprintf(stderr, _("%s: Cannot open %s: %s\n"),
				progname, fc_name, fc_e);
		exit(EXIT_FAILURE);
	}
	fc_wantcont = false;
	for (fc_num = 1;; ++fc_num)
	{
		fc_eat(fc_name, fc_num);
		if (fgets(fc_buf, sizeof fc_buf, fc_fp) != fc_buf)
			break;
		fc_cp = strchr(fc_buf, '\n');
		if (fc_cp == NULL)
		{
			fc_error(_("line too long"));
			exit(EXIT_FAILURE);
		}
		*fc_cp = '\0';
		fc_fields = fc_getfields(fc_buf);
		fc_nfields = 0;
		while (fc_fields[fc_nfields] != NULL)
		{
			static char fc_nada;

			if (strcmp(fc_fields[fc_nfields], "-") == 0)
				fc_fields[fc_nfields] = &fc_nada;
			++fc_nfields;
		}
		if (fc_nfields == 0)
		{
			if (fc_name == leapsec && *fc_buf == '#')
			{
				/*
				 * PG: INT64_FORMAT在sscanf中不是可移植的，因此要满足
				 * 于扫描“long”。 一旦我们在所有活跃分支中要求C99，
				 * 采用上游的做法使用<inttypes.h>宏会更合理。 但是目前，我们
				 * 并不实际使用这段代码，而且在2038年之前不会溢出。
				 */
				long		fc_cl_tmp;

				sscanf(fc_buf, "#expires %ld", &fc_cl_tmp);
				comment_leapexpires = fc_cl_tmp;
			}
		}
		else if (fc_wantcont)
		{
			fc_wantcont = fc_inzcont(fc_fields, fc_nfields);
		}
		else
		{
			struct lookup const *fc_line_codes
			= fc_name == leapsec ? leap_line_codes : zi_line_codes;

			fc_lp = fc_byword(fc_fields[0], fc_line_codes);
			if (fc_lp == NULL)
				fc_error(_("input line of unknown type"));
			else
				switch (fc_lp->l_value)
				{
					case LC_RULE:
						fc_inrule(fc_fields, fc_nfields);
						fc_wantcont = false;
						break;
					case LC_ZONE:
						fc_wantcont = fc_inzone(fc_fields, fc_nfields);
						break;
					case LC_LINK:
						fc_inlink(fc_fields, fc_nfields);
						fc_wantcont = false;
						break;
					case LC_LEAP:
						fc_inleap(fc_fields, fc_nfields);
						fc_wantcont = false;
						break;
					case LC_EXPIRES:
						fc_inexpires(fc_fields, fc_nfields);
						fc_wantcont = false;
						break;
					default:	/* "无法发生" */
						fprintf(stderr,
								_("%s: panic: Invalid l_value %d\n"),
								progname, fc_lp->l_value);
						exit(EXIT_FAILURE);
				}
		}
		free(fc_fields);
	}
	fc_close_file(fc_fp, NULL, filename);
	if (fc_wantcont)
		fc_error(_("expected continuation line not found"));
}

/*
 * 将以下形式的字符串
 *	h	-h	hh:mm	-hh:mm	hh:mm:ss	-hh:mm:ss
 * 转换为秒数。
 * 空字符串映射为零。
 * 出错时调用error并传递errstring并返回零。
 */

static zic_t fc_gethms(char const *fc_string, char const *fc_errstring)
{
	/* PG: 使hh成为int而不是zic_t以避免sscanf可移植性问题 */
	int			fc_hh;
	int			fc_sign,
				fc_mm = 0,
				fc_ss = 0;
	char		fc_hhx,
				fc_mmx,
				fc_ssx,
				fc_xr = '0',
				fc_xs;
	int			fc_tenths = 0;
	bool		fc_ok = true;

	if (fc_string == NULL || *fc_string == '\0')
		return 0;
	if (*fc_string == '-')
	{
		fc_sign = -1;
		++fc_string;
	}
	else
		fc_sign = 1;
	switch (sscanf(fc_string,
				   "%d%c%d%c%d%c%1d%*[0]%c%*[0123456789]%c",
				   &fc_hh, &fc_hhx, &fc_mm, &fc_mmx, &fc_ss, &fc_ssx, &fc_tenths, &fc_xr, &fc_xs))
	{
		default:
			fc_ok = false;
			break;
		case 8:
			fc_ok = '0' <= fc_xr && fc_xr <= '9';
			/* fall through */
		case 7:
			fc_ok &= fc_ssx == '.';
			if (fc_ok && noise)
				fc_warning(_("fractional seconds rejected by"
						  " pre-2018 versions of zic"));
			/* fall through */
		case 5:
			fc_ok &= fc_mmx == ':';
			/* fall through */
		case 3:
			fc_ok &= fc_hhx == ':';
			/* fall through */
		case 1:
			break;
	}
	if (!fc_ok)
	{
		fc_error("%s", fc_errstring);
		return 0;
	}
	if (fc_hh < 0 ||
		fc_mm < 0 || fc_mm >= MINSPERHOUR ||
		fc_ss < 0 || fc_ss > SECSPERMIN)
	{
		fc_error("%s", fc_errstring);
		return 0;
	}
	/* 一些编译器警告此测试对于32位整数是不可满足的 */
#if INT_MAX > PG_INT32_MAX
	if (ZIC_MAX / SECSPERHOUR < hh)
	{
		error(_("time overflow"));
		return 0;
	}
#endif
	fc_ss += 5 + ((fc_ss ^ 1) & (fc_xr == '0')) <= fc_tenths;	/* 进行舍入到偶数。  */
	if (noise && (fc_hh > HOURSPERDAY ||
				  (fc_hh == HOURSPERDAY && (fc_mm != 0 || fc_ss != 0))))
		fc_warning(_("values over 24 hours not handled by pre-2007 versions of zic"));
	return fc_oadd(fc_sign * (zic_t) fc_hh * SECSPERHOUR,
				fc_sign * (fc_mm * SECSPERMIN + fc_ss));
}

static zic_t fc_getsave(char *fc_field, bool *fc_isdst)
{
	int			fc_dst = -1;
	zic_t		fc_save;
	size_t		fc_fieldlen = strlen(fc_field);

	if (fc_fieldlen != 0)
	{
		char	   *fc_ep = fc_field + fc_fieldlen - 1;

		switch (*fc_ep)
		{
			case 'd':
				fc_dst = 1;
				*fc_ep = '\0';
				break;
			case 's':
				fc_dst = 0;
				*fc_ep = '\0';
				break;
		}
	}
	fc_save = fc_gethms(fc_field, _("invalid saved time"));
	*fc_isdst = fc_dst < 0 ? fc_save != 0 : fc_dst;
	return fc_save;
}

static void fc_inrule(char **fc_fields, int fc_nfields)
{
	static struct rule fc_r;

	if (fc_nfields != RULE_FIELDS)
	{
		fc_error(_("wrong number of fields on Rule line"));
		return;
	}
	switch (*fc_fields[RF_NAME])
	{
		case '\0':
		case ' ':
		case '\f':
		case '\n':
		case '\r':
		case '\t':
		case '\v':
		case '+':
		case '-':
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			fc_error(_("Invalid rule name \"%s\""), fc_fields[RF_NAME]);
			return;
	}
	fc_r.r_filename = filename;
	fc_r.r_linenum = linenum;
	fc_r.r_save = fc_getsave(fc_fields[RF_SAVE], &fc_r.r_isdst);
	fc_rulesub(&fc_r, fc_fields[RF_LOYEAR], fc_fields[RF_HIYEAR], fc_fields[RF_COMMAND],
			fc_fields[RF_MONTH], fc_fields[RF_DAY], fc_fields[RF_TOD]);
	fc_r.r_name = fc_ecpyalloc(fc_fields[RF_NAME]);
	fc_r.r_abbrvar = fc_ecpyalloc(fc_fields[RF_ABBRVAR]);
	if (max_abbrvar_len < strlen(fc_r.r_abbrvar))
		max_abbrvar_len = strlen(fc_r.r_abbrvar);
	rules = fc_growalloc(rules, sizeof *rules, nrules, &nrules_alloc);
	rules[nrules++] = fc_r;
}

static bool fc_inzone(char **fc_fields, int fc_nfields)
{
	ptrdiff_t	fc_i;

	if (fc_nfields < ZONE_MINFIELDS || fc_nfields > ZONE_MAXFIELDS)
	{
		fc_error(_("wrong number of fields on Zone line"));
		return false;
	}
	if (lcltime != NULL && strcmp(fc_fields[ZF_NAME], tzdefault) == 0)
	{
		fc_error(
			  _("\"Zone %s\" line and -l option are mutually exclusive"),
			  tzdefault);
		return false;
	}
	if (strcmp(fc_fields[ZF_NAME], TZDEFRULES) == 0 && psxrules != NULL)
	{
		fc_error(
			  _("\"Zone %s\" line and -p option are mutually exclusive"),
			  TZDEFRULES);
		return false;
	}
	for (fc_i = 0; fc_i < nzones; ++fc_i)
		if (zones[fc_i].z_name != NULL &&
			strcmp(zones[fc_i].z_name, fc_fields[ZF_NAME]) == 0)
		{
			fc_error(_("duplicate zone name %s"
					" (file \"%s\", line %d)"),
				  fc_fields[ZF_NAME],
				  zones[fc_i].z_filename,
				  zones[fc_i].z_linenum);
			return false;
		}
	return fc_inzsub(fc_fields, fc_nfields, false);
}

static bool fc_inzcont(char **fc_fields, int fc_nfields)
{
	if (fc_nfields < ZONEC_MINFIELDS || fc_nfields > ZONEC_MAXFIELDS)
	{
		fc_error(_("wrong number of fields on Zone continuation line"));
		return false;
	}
	return fc_inzsub(fc_fields, fc_nfields, true);
}

static bool fc_inzsub(char **fc_fields, int fc_nfields, bool fc_iscont)
{
	char	   *fc_cp;
	char	   *fc_cp1;
	static struct zone fc_z;
	int			fc_i_stdoff,
				fc_i_rule,
				fc_i_format;
	int			fc_i_untilyear,
				fc_i_untilmonth;
	int			fc_i_untilday,
				fc_i_untiltime;
	bool		fc_hasuntil;

	if (fc_iscont)
	{
		fc_i_stdoff = ZFC_STDOFF;
		fc_i_rule = ZFC_RULE;
		fc_i_format = ZFC_FORMAT;
		fc_i_untilyear = ZFC_TILYEAR;
		fc_i_untilmonth = ZFC_TILMONTH;
		fc_i_untilday = ZFC_TILDAY;
		fc_i_untiltime = ZFC_TILTIME;
		fc_z.z_name = NULL;
	}
	else if (!fc_namecheck(fc_fields[ZF_NAME]))
		return false;
	else
	{
		fc_i_stdoff = ZF_STDOFF;
		fc_i_rule = ZF_RULE;
		fc_i_format = ZF_FORMAT;
		fc_i_untilyear = ZF_TILYEAR;
		fc_i_untilmonth = ZF_TILMONTH;
		fc_i_untilday = ZF_TILDAY;
		fc_i_untiltime = ZF_TILTIME;
		fc_z.z_name = fc_ecpyalloc(fc_fields[ZF_NAME]);
	}
	fc_z.z_filename = filename;
	fc_z.z_linenum = linenum;
	fc_z.z_stdoff = fc_gethms(fc_fields[fc_i_stdoff], _("invalid UT offset"));
	if ((fc_cp = strchr(fc_fields[fc_i_format], '%')) != NULL)
	{
		if ((*++fc_cp != 's' && *fc_cp != 'z') || strchr(fc_cp, '%')
			|| strchr(fc_fields[fc_i_format], '/'))
		{
			fc_error(_("invalid abbreviation format"));
			return false;
		}
	}
	fc_z.z_rule = fc_ecpyalloc(fc_fields[fc_i_rule]);
	fc_z.z_format = fc_cp1 = fc_ecpyalloc(fc_fields[fc_i_format]);
	fc_z.z_format_specifier = fc_cp ? *fc_cp : '\0';
	if (fc_z.z_format_specifier == 'z')
	{
		if (noise)
			fc_warning(_("format '%s' not handled by pre-2015 versions of zic"),
					fc_z.z_format);
		fc_cp1[fc_cp - fc_fields[fc_i_format]] = 's';
	}
	if (max_format_len < strlen(fc_z.z_format))
		max_format_len = strlen(fc_z.z_format);
	fc_hasuntil = fc_nfields > fc_i_untilyear;
	if (fc_hasuntil)
	{
		fc_z.z_untilrule.r_filename = filename;
		fc_z.z_untilrule.r_linenum = linenum;
		fc_rulesub(&fc_z.z_untilrule,
				fc_fields[fc_i_untilyear],
				"only",
				"",
				(fc_nfields > fc_i_untilmonth) ?
				fc_fields[fc_i_untilmonth] : "Jan",
				(fc_nfields > fc_i_untilday) ? fc_fields[fc_i_untilday] : "1",
				(fc_nfields > fc_i_untiltime) ? fc_fields[fc_i_untiltime] : "0");
		fc_z.z_untiltime = fc_rpytime(&fc_z.z_untilrule,
								fc_z.z_untilrule.r_loyear);
		if (fc_iscont && nzones > 0 &&
			fc_z.z_untiltime > min_time &&
			fc_z.z_untiltime < max_time &&
			zones[nzones - 1].z_untiltime > min_time &&
			zones[nzones - 1].z_untiltime < max_time &&
			zones[nzones - 1].z_untiltime >= fc_z.z_untiltime)
		{
			fc_error(_("Zone continuation line end time is not after end time of previous line"));
			return false;
		}
	}
	zones = fc_growalloc(zones, sizeof *zones, nzones, &nzones_alloc);
	zones[nzones++] = fc_z;

	/*
	 * 如果这一行有UNTIL字段，下一行
	 * 有关于该区域的更多信息。
	 */
	return fc_hasuntil;
}

static zic_t fc_getleapdatetime(char **fc_fields, int fc_nfields, bool fc_expire_line)
{
	const char *fc_cp;
	const struct lookup *fc_lp;
	zic_t		fc_i,
				fc_j;

	/* PG: 使年份成为int而不是zic_t以避免sscanf可移植性问题 */
	int			fc_year;
	int			fc_month,
				fc_day;
	zic_t		fc_dayoff,
				fc_tod;
	zic_t		fc_t;
	char		fc_xs;

	fc_dayoff = 0;
	fc_cp = fc_fields[LP_YEAR];
	if (sscanf(fc_cp, "%d%c", &fc_year, &fc_xs) != 1)
	{
		/*
		 * 天哪！
		 */
		fc_error(_("invalid leaping year"));
		return -1;
	}
	if (!fc_expire_line)
	{
		if (!leapseen || leapmaxyear < fc_year)
			leapmaxyear = fc_year;
		if (!leapseen || leapminyear > fc_year)
			leapminyear = fc_year;
		leapseen = true;
	}
	fc_j = EPOCH_YEAR;
	while (fc_j != fc_year)
	{
		if (fc_year > fc_j)
		{
			fc_i = len_years[isleap(fc_j)];
			++fc_j;
		}
		else
		{
			--fc_j;
			fc_i = -len_years[isleap(fc_j)];
		}
		fc_dayoff = fc_oadd(fc_dayoff, fc_i);
	}
	if ((fc_lp = fc_byword(fc_fields[LP_MONTH], mon_names)) == NULL)
	{
		fc_error(_("invalid month name"));
		return -1;
	}
	fc_month = fc_lp->l_value;
	fc_j = TM_JANUARY;
	while (fc_j != fc_month)
	{
		fc_i = len_months[isleap(fc_year)][fc_j];
		fc_dayoff = fc_oadd(fc_dayoff, fc_i);
		++fc_j;
	}
	fc_cp = fc_fields[LP_DAY];
	if (sscanf(fc_cp, "%d%c", &fc_day, &fc_xs) != 1 ||
		fc_day <= 0 || fc_day > len_months[isleap(fc_year)][fc_month])
	{
		fc_error(_("invalid day of month"));
		return -1;
	}
	fc_dayoff = fc_oadd(fc_dayoff, fc_day - 1);
	if (fc_dayoff < min_time / SECSPERDAY)
	{
		fc_error(_("time too small"));
		return -1;
	}
	if (fc_dayoff > max_time / SECSPERDAY)
	{
		fc_error(_("time too large"));
		return -1;
	}
	fc_t = fc_dayoff * SECSPERDAY;
	fc_tod = fc_gethms(fc_fields[LP_TIME], _("invalid time of day"));
	fc_t = fc_tadd(fc_t, fc_tod);
	if (fc_t < 0)
		fc_error(_("leap second precedes Epoch"));
	return fc_t;
}

static void fc_inleap(char **fc_fields, int fc_nfields)
{
	if (fc_nfields != LEAP_FIELDS)
		fc_error(_("wrong number of fields on Leap line"));
	else
	{
		zic_t		fc_t = fc_getleapdatetime(fc_fields, fc_nfields, false);

		if (0 <= fc_t)
		{
			struct lookup const *fc_lp = fc_byword(fc_fields[LP_ROLL], leap_types);

			if (!fc_lp)
				fc_error(_("invalid Rolling/Stationary field on Leap line"));
			else
			{
				int			fc_correction = 0;

				if (!fc_fields[LP_CORR][0])	/* infile()将“-”变为“”。  */
					fc_correction = -1;
				else if (strcmp(fc_fields[LP_CORR], "+") == 0)
					fc_correction = 1;
				else
					fc_error(_("invalid CORRECTION field on Leap line"));
				if (fc_correction)
					fc_leapadd(fc_t, fc_correction, fc_lp->l_value);
			}
		}
	}
}

static void fc_inexpires(char **fc_fields, int fc_nfields)
{
	if (fc_nfields != EXPIRES_FIELDS)
		fc_error(_("wrong number of fields on Expires line"));
	else if (0 <= leapexpires)
		fc_error(_("multiple Expires lines"));
	else
		leapexpires = fc_getleapdatetime(fc_fields, fc_nfields, true);
}

static void fc_inlink(char **fc_fields, int fc_nfields)
{
	struct link fc_l;

	if (fc_nfields != LINK_FIELDS)
	{
		fc_error(_("wrong number of fields on Link line"));
		return;
	}
	if (*fc_fields[LF_TARGET] == '\0')
	{
		fc_error(_("blank TARGET field on Link line"));
		return;
	}
	if (!fc_namecheck(fc_fields[LF_LINKNAME]))
		return;
	fc_l.l_filename = filename;
	fc_l.l_linenum = linenum;
	fc_l.l_target = fc_ecpyalloc(fc_fields[LF_TARGET]);
	fc_l.l_linkname = fc_ecpyalloc(fc_fields[LF_LINKNAME]);
	links = fc_growalloc(links, sizeof *links, nlinks, &nlinks_alloc);
	links[nlinks++] = fc_l;
}

static void fc_rulesub(struct rule *fc_rp, const char *fc_loyearp, const char *fc_hiyearp,
		const char *fc_typep, const char *fc_monthp, const char *fc_dayp,
		const char *fc_timep)
{
	const struct lookup *fc_lp;
	const char *fc_cp;
	char	   *fc_dp;
	char	   *fc_ep;
	char		fc_xs;

	/* PG: year_tmp是为了避免sscanf可移植性问题 */
	int			fc_year_tmp;

	if ((fc_lp = fc_byword(fc_monthp, mon_names)) == NULL)
	{
		fc_error(_("invalid month name"));
		return;
	}
	fc_rp->r_month = fc_lp->l_value;
	fc_rp->r_todisstd = false;
	fc_rp->r_todisut = false;
	fc_dp = fc_ecpyalloc(fc_timep);
	if (*fc_dp != '\0')
	{
		fc_ep = fc_dp + strlen(fc_dp) - 1;
		switch (fc_lowerit(*fc_ep))
		{
			case 's':			/* 标准 */
				fc_rp->r_todisstd = true;
				fc_rp->r_todisut = false;
				*fc_ep = '\0';
				break;
			case 'w':			/* 墙 */
				fc_rp->r_todisstd = false;
				fc_rp->r_todisut = false;
				*fc_ep = '\0';
				break;
			case 'g':			/* 格林威治 */
			case 'u':			/* 通用 */
			case 'z':			/* 祖鲁 */
				fc_rp->r_todisstd = true;
				fc_rp->r_todisut = true;
				*fc_ep = '\0';
				break;
		}
	}
	fc_rp->r_tod = fc_gethms(fc_dp, _("invalid time of day"));
	free(fc_dp);

	/*
	 * 年份处理。
	 */
	fc_cp = fc_loyearp;
	fc_lp = fc_byword(fc_cp, begin_years);
	fc_rp->r_lowasnum = fc_lp == NULL;
	if (!fc_rp->r_lowasnum)
		switch (fc_lp->l_value)
		{
			case YR_MINIMUM:
				fc_rp->r_loyear = ZIC_MIN;
				break;
			case YR_MAXIMUM:
				fc_rp->r_loyear = ZIC_MAX;
				break;
			default:			/* "无法发生" */
				fprintf(stderr,
						_("%s: panic: Invalid l_value %d\n"),
						progname, fc_lp->l_value);
				exit(EXIT_FAILURE);
		}
	else if (sscanf(fc_cp, "%d%c", &fc_year_tmp, &fc_xs) == 1)
		fc_rp->r_loyear = fc_year_tmp;
	else
	{
		fc_error(_("invalid starting year"));
		return;
	}
	fc_cp = fc_hiyearp;
	fc_lp = fc_byword(fc_cp, end_years);
	fc_rp->r_hiwasnum = fc_lp == NULL;
	if (!fc_rp->r_hiwasnum)
		switch (fc_lp->l_value)
		{
			case YR_MINIMUM:
				fc_rp->r_hiyear = ZIC_MIN;
				break;
			case YR_MAXIMUM:
				fc_rp->r_hiyear = ZIC_MAX;
				break;
			case YR_ONLY:
				fc_rp->r_hiyear = fc_rp->r_loyear;
				break;
			default:			/* "无法发生" */
				fprintf(stderr,
						_("%s: panic: Invalid l_value %d\n"),
						progname, fc_lp->l_value);
				exit(EXIT_FAILURE);
		}
	else if (sscanf(fc_cp, "%d%c", &fc_year_tmp, &fc_xs) == 1)
		fc_rp->r_hiyear = fc_year_tmp;
	else
	{
		fc_error(_("invalid ending year"));
		return;
	}
	if (fc_rp->r_loyear > fc_rp->r_hiyear)
	{
		fc_error(_("starting year greater than ending year"));
		return;
	}
	if (*fc_typep != '\0')
	{
		fc_error(_("year type \"%s\" is unsupported; use \"-\" instead"),
			  fc_typep);
		return;
	}

	/*
	 * 日期处理。 接受诸如：1 lastSunday last-Sunday
	 * (未记录；对此发出警告) Sun<=20 Sun>=7
	 */
	fc_dp = fc_ecpyalloc(fc_dayp);
	if ((fc_lp = fc_byword(fc_dp, lasts)) != NULL)
	{
		fc_rp->r_dycode = DC_DOWLEQ;
		fc_rp->r_wday = fc_lp->l_value;
		fc_rp->r_dayofmonth = len_months[1][fc_rp->r_month];
	}
	else
	{
		if ((fc_ep = strchr(fc_dp, '<')) != NULL)
			fc_rp->r_dycode = DC_DOWLEQ;
		else if ((fc_ep = strchr(fc_dp, '>')) != NULL)
			fc_rp->r_dycode = DC_DOWGEQ;
		else
		{
			fc_ep = fc_dp;
			fc_rp->r_dycode = DC_DOM;
		}
		if (fc_rp->r_dycode != DC_DOM)
		{
			*fc_ep++ = 0;
			if (*fc_ep++ != '=')
			{
				fc_error(_("invalid day of month"));
				free(fc_dp);
				return;
			}
			if ((fc_lp = fc_byword(fc_dp, wday_names)) == NULL)
			{
				fc_error(_("invalid weekday name"));
				free(fc_dp);
				return;
			}
			fc_rp->r_wday = fc_lp->l_value;
		}
		if (sscanf(fc_ep, "%d%c", &fc_rp->r_dayofmonth, &fc_xs) != 1 ||
			fc_rp->r_dayofmonth <= 0 ||
			(fc_rp->r_dayofmonth > len_months[1][fc_rp->r_month]))
		{
			fc_error(_("invalid day of month"));
			free(fc_dp);
			return;
		}
	}
	free(fc_dp);
}

static void fc_convert(const int32 fc_val, char *const fc_buf)
{
	int			fc_i;
	int			fc_shift;
	unsigned char *const fc_b = (unsigned char *) fc_buf;

	for (fc_i = 0, fc_shift = 24; fc_i < 4; ++fc_i, fc_shift -= 8)
		fc_b[fc_i] = fc_val >> fc_shift;
}

static void fc_convert64(const zic_t fc_val, char *const fc_buf)
{
	int			fc_i;
	int			fc_shift;
	unsigned char *const fc_b = (unsigned char *) fc_buf;

	for (fc_i = 0, fc_shift = 56; fc_i < 8; ++fc_i, fc_shift -= 8)
		fc_b[fc_i] = fc_val >> fc_shift;
}

static void fc_puttzcode(const int32 fc_val, FILE *const fc_fp)
{
	char		fc_buf[4];

	fc_convert(fc_val, fc_buf);
	fwrite(fc_buf, sizeof fc_buf, 1, fc_fp);
}

static void fc_puttzcodepass(zic_t fc_val, FILE *fc_fp, int fc_pass)
{
	if (fc_pass == 1)
		fc_puttzcode(fc_val, fc_fp);
	else
	{
		char		fc_buf[8];

		fc_convert64(fc_val, fc_buf);
		fwrite(fc_buf, sizeof fc_buf, 1, fc_fp);
	}
}

static int fc_atcomp(const void *fc_avp, const void *fc_bvp)
{
	const zic_t fc_a = ((const struct attype *) fc_avp)->at;
	const zic_t fc_b = ((const struct attype *) fc_bvp)->at;

	return (fc_a < fc_b) ? -1 : (fc_a > fc_b);
}

struct timerange
{
	int			defaulttype;
	ptrdiff_t	base,
				count;
	int			leapbase,
				leapcount;
};

static struct timerange
fc_limitrange(struct timerange fc_r, zic_t fc_lo, zic_t fc_hi,
		   zic_t const *fc_ats, unsigned char const *fc_types)
{
	while (0 < fc_r.count && fc_ats[fc_r.base] < fc_lo)
	{
		fc_r.defaulttype = fc_types[fc_r.base];
		fc_r.count--;
		fc_r.base++;
	}
	while (0 < fc_r.leapcount && trans[fc_r.leapbase] < fc_lo)
	{
		fc_r.leapcount--;
		fc_r.leapbase++;
	}

	if (fc_hi < ZIC_MAX)
	{
		while (0 < fc_r.count && fc_hi + 1 < fc_ats[fc_r.base + fc_r.count - 1])
			fc_r.count--;
		while (0 < fc_r.leapcount && fc_hi + 1 < trans[fc_r.leapbase + fc_r.leapcount - 1])
			fc_r.leapcount--;
	}

	return fc_r;
}

static void fc_writezone(const char *const fc_name, const char *const fc_string, char fc_version,
		  int fc_defaulttype)
{
	FILE	   *fc_fp;
	ptrdiff_t	fc_i,
				fc_j;
	int			fc_pass;
	static const struct tzhead fc_tzh0;
	static struct tzhead fc_tzh;
	bool		fc_dir_checked = false;
	zic_t		fc_one = 1;
	zic_t		fc_y2038_boundary = fc_one << 31;
	ptrdiff_t	fc_nats = timecnt + WORK_AROUND_QTBUG_53071;

	/*
	 * 通过单个malloc分配ATS和TYPES数组，因为这样会更快一些。
	 */
	zic_t	   *fc_ats = fc_emalloc(MAXALIGN(fc_size_product(fc_nats, sizeof *fc_ats + 1)));
	void	   *fc_typesptr = fc_ats + fc_nats;
	unsigned char *fc_types = fc_typesptr;
	struct timerange fc_rangeall,
				fc_range32,
				fc_range64;

	/*
	 * 排序。
	 */
	if (timecnt > 1)
		qsort(attypes, timecnt, sizeof *attypes, fc_atcomp);

	/*
	 * 优化。
	 */
	{
		ptrdiff_t	fc_fromi,
					fc_toi;

		fc_toi = 0;
		fc_fromi = 0;
		for (; fc_fromi < timecnt; ++fc_fromi)
		{
			if (fc_toi != 0
				&& ((attypes[fc_fromi].at
					 + utoffs[attypes[fc_toi - 1].type])
					<= (attypes[fc_toi - 1].at
						+ utoffs[fc_toi == 1 ? 0
								 : attypes[fc_toi - 2].type])))
			{
				attypes[fc_toi - 1].type =
					attypes[fc_fromi].type;
				continue;
			}
			if (fc_toi == 0
				|| attypes[fc_fromi].dontmerge
				|| (utoffs[attypes[fc_toi - 1].type]
					!= utoffs[attypes[fc_fromi].type])
				|| (isdsts[attypes[fc_toi - 1].type]
					!= isdsts[attypes[fc_fromi].type])
				|| (desigidx[attypes[fc_toi - 1].type]
					!= desigidx[attypes[fc_fromi].type]))
				attypes[fc_toi++] = attypes[fc_fromi];
		}
		timecnt = fc_toi;
	}

	if (noise && timecnt > 1200)
	{
		if (timecnt > TZ_MAX_TIMES)
			fc_warning(_("reference clients mishandle"
					  " more than %d transition times"),
					TZ_MAX_TIMES);
		else
			fc_warning(_("pre-2014 clients may mishandle"
					  " more than 1200 transition times"));
	}

	/*
	 * 转移。
	 */
	for (fc_i = 0; fc_i < timecnt; ++fc_i)
	{
		fc_ats[fc_i] = attypes[fc_i].at;
		fc_types[fc_i] = attypes[fc_i].type;
	}

	/*
	 * 修正闰秒。
	 */
	for (fc_i = 0; fc_i < timecnt; ++fc_i)
	{
		fc_j = leapcnt;
		while (--fc_j >= 0)
			if (fc_ats[fc_i] > trans[fc_j] - corr[fc_j])
			{
				fc_ats[fc_i] = fc_tadd(fc_ats[fc_i], corr[fc_j]);
				break;
			}
	}

	/*
	 * 针对时间戳小于 y2038_boundary - 1 的 QTBUG-53071 进行变通，
	 * 通过在 y2038_boundary - 1 时插入一个无操作过渡。这
	 * 仅适用于边界之前的时间戳，这在实际中应该足够好，
	 * 因为 QTBUG-53071 到 2038 年应该早已消失。
	 * 在修正闰秒后再进行此操作，因为目的是在 32 位
	 * pg_time_t 回绕之前插入一个过渡，而如果过渡
	 * 已进行闰秒修正，则发生的时间会稍微有所不同。
	 */
	if (WORK_AROUND_QTBUG_53071 && timecnt != 0 && fc_want_bloat()
		&& fc_ats[timecnt - 1] < fc_y2038_boundary - 1 && strchr(fc_string, '<'))
	{
		fc_ats[timecnt] = fc_y2038_boundary - 1;
		fc_types[timecnt] = fc_types[timecnt - 1];
		timecnt++;
	}

	fc_rangeall.defaulttype = fc_defaulttype;
	fc_rangeall.base = fc_rangeall.leapbase = 0;
	fc_rangeall.count = timecnt;
	fc_rangeall.leapcount = leapcnt;
	fc_range64 = fc_limitrange(fc_rangeall, lo_time, hi_time, fc_ats, fc_types);
	fc_range32 = fc_limitrange(fc_range64, PG_INT32_MIN, PG_INT32_MAX, fc_ats, fc_types);

	/*
	 * 如果有旧文件，移除它以快速链接。
	 */
	if (remove(fc_name) == 0)
		fc_dir_checked = true;
	else if (errno != ENOENT)
	{
		const char *fc_e = strerror(errno);

		fprintf(stderr, _("%s: Cannot remove %s/%s: %s\n"),
				progname, directory, fc_name, fc_e);
		exit(EXIT_FAILURE);
	}
	fc_fp = fopen(fc_name, "wb");
	if (!fc_fp)
	{
		int			fc_fopen_errno = errno;

		if (fc_fopen_errno == ENOENT && !fc_dir_checked)
		{
			fc_mkdirs(fc_name, true);
			fc_fp = fopen(fc_name, "wb");
			fc_fopen_errno = errno;
		}
		if (!fc_fp)
		{
			fprintf(stderr, _("%s: Cannot create %s/%s: %s\n"),
					progname, directory, fc_name, strerror(fc_fopen_errno));
			exit(EXIT_FAILURE);
		}
	}
	for (fc_pass = 1; fc_pass <= 2; ++fc_pass)
	{
		ptrdiff_t	fc_thistimei,
					fc_thistimecnt,
					fc_thistimelim;
		int			fc_thisleapi,
					fc_thisleapcnt,
					fc_thisleaplim;
		int			fc_currenttype,
					fc_thisdefaulttype;
		bool		fc_locut,
					fc_hicut;
		zic_t		fc_lo;
		int			fc_old0;
		char		fc_omittype[TZ_MAX_TYPES];
		int			fc_typemap[TZ_MAX_TYPES];
		int			fc_thistypecnt,
					fc_stdcnt,
					fc_utcnt;
		char		fc_thischars[TZ_MAX_CHARS];
		int			fc_thischarcnt;
		bool		fc_toomanytimes;
		int			fc_indmap[TZ_MAX_CHARS];

		if (fc_pass == 1)
		{
			/*
			 * 可以说，32 位数据中的默认时间类型应为
			 * range32.defaulttype，这适合于 PG_INT32_MIN
			 * 之前的时间戳。然而，zic 传统上使用的是
			 * 不确定的过去的时间类型。互联网 RFC 8532
			 * 表示读者应忽略 32 位数据，因此这种差异
			 * 仅对过时的读者有意义，传统类型在某些情况下
			 * 可能更合适，即使它是“错误的”。因此，使用
			 * 历史 zic 值，除非 -r 指定了排除某些
			 * 32 位时间戳的低截止值。
			 */
			fc_thisdefaulttype = (lo_time <= PG_INT32_MIN
							   ? fc_range64.defaulttype
							   : fc_range32.defaulttype);

			fc_thistimei = fc_range32.base;
			fc_thistimecnt = fc_range32.count;
			fc_toomanytimes = fc_thistimecnt >> 31 >> 1 != 0;
			fc_thisleapi = fc_range32.leapbase;
			fc_thisleapcnt = fc_range32.leapcount;
			fc_locut = PG_INT32_MIN < lo_time;
			fc_hicut = hi_time < PG_INT32_MAX;
		}
		else
		{
			fc_thisdefaulttype = fc_range64.defaulttype;
			fc_thistimei = fc_range64.base;
			fc_thistimecnt = fc_range64.count;
			fc_toomanytimes = fc_thistimecnt >> 31 >> 31 >> 2 != 0;
			fc_thisleapi = fc_range64.leapbase;
			fc_thisleapcnt = fc_range64.leapcount;
			fc_locut = min_time < lo_time;
			fc_hicut = hi_time < max_time;
		}
		if (fc_toomanytimes)
			fc_error(_("too many transition times"));

		/*
		 * 如果没有过渡恰好在 LO 处，则保留最后一个过低的过渡。
		 * 保留的过渡将作为 LO "transition" 输出；
		 * 参见下面的“输出一个 LO_TIME 过渡”。当输出
		 * 在开始时被截断时，这是必要的，同时在针对
		 * 不使用类型 0 的 32 位客户端时也很有用。
		 */
		if (0 < fc_thistimei && fc_ats[fc_thistimei] != lo_time)
		{
			fc_thistimei--;
			fc_thistimecnt++;
			fc_locut = false;
		}

		fc_thistimelim = fc_thistimei + fc_thistimecnt;
		fc_thisleaplim = fc_thisleapi + fc_thisleapcnt;
		if (fc_thistimecnt != 0)
		{
			if (fc_ats[fc_thistimei] == lo_time)
				fc_locut = false;
			if (hi_time < ZIC_MAX && fc_ats[fc_thistimelim - 1] == hi_time + 1)
				fc_hicut = false;
		}
		memset(fc_omittype, true, typecnt);
		fc_omittype[fc_thisdefaulttype] = false;
		for (fc_i = fc_thistimei; fc_i < fc_thistimelim; fc_i++)
			fc_omittype[fc_types[fc_i]] = false;

		/*
		 * 重新排序类型，以使 THISDEFAULTTYPE 为类型 0。
		 * 使用 TYPEMAP 交换 OLD0 和 THISDEFAULTTYPE，
		 * 以便在输出中将 THISDEFAULTTYPE 显示为类型 0
		 * 而不是 OLD0。TYPEMAP 还会省略未使用的类型。
		 */
		fc_old0 = strlen(fc_omittype);

#ifndef LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH

		/*
		 * 对于一些 2011 年之前的系统：如果最后写入的
		 * 标准（或夏令时）类型的偏移量与最近使用的
		 * 偏移量不同，则附加最近使用类型的（未使用）
		 * 副本（以帮助正确设置全局 "altzone" 和
		 * "timezone" 变量）。
		 */
		if (fc_want_bloat())
		{
			int			fc_mrudst,
						fc_mrustd,
						fc_hidst,
						fc_histd,
						fc_type;

			fc_hidst = fc_histd = fc_mrudst = fc_mrustd = -1;
			for (fc_i = fc_thistimei; fc_i < fc_thistimelim; ++fc_i)
				if (isdsts[fc_types[fc_i]])
					fc_mrudst = fc_types[fc_i];
				else
					fc_mrustd = fc_types[fc_i];
			for (fc_i = fc_old0; fc_i < typecnt; fc_i++)
			{
				int			fc_h = (fc_i == fc_old0 ? fc_thisdefaulttype
								 : fc_i == fc_thisdefaulttype ? fc_old0 : fc_i);

				if (!fc_omittype[fc_h])
				{
					if (isdsts[fc_h])
						fc_hidst = fc_i;
					else
						fc_histd = fc_i;
				}
			}
			if (fc_hidst >= 0 && fc_mrudst >= 0 && fc_hidst != fc_mrudst &&
				utoffs[fc_hidst] != utoffs[fc_mrudst])
			{
				isdsts[fc_mrudst] = -1;
				fc_type = fc_addtype(utoffs[fc_mrudst],
							   &chars[desigidx[fc_mrudst]],
							   true,
							   ttisstds[fc_mrudst],
							   ttisuts[fc_mrudst]);
				isdsts[fc_mrudst] = 1;
				fc_omittype[fc_type] = false;
			}
			if (fc_histd >= 0 && fc_mrustd >= 0 && fc_histd != fc_mrustd &&
				utoffs[fc_histd] != utoffs[fc_mrustd])
			{
				isdsts[fc_mrustd] = -1;
				fc_type = fc_addtype(utoffs[fc_mrustd],
							   &chars[desigidx[fc_mrustd]],
							   false,
							   ttisstds[fc_mrustd],
							   ttisuts[fc_mrustd]);
				isdsts[fc_mrustd] = 0;
				fc_omittype[fc_type] = false;
			}
		}
#endif							/* !defined
								 * LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH */
		fc_thistypecnt = 0;
		for (fc_i = fc_old0; fc_i < typecnt; fc_i++)
			if (!fc_omittype[fc_i])
				fc_typemap[fc_i == fc_old0 ? fc_thisdefaulttype
						: fc_i == fc_thisdefaulttype ? fc_old0 : fc_i]
					= fc_thistypecnt++;

		for (fc_i = 0; fc_i < sizeof fc_indmap / sizeof fc_indmap[0]; ++fc_i)
			fc_indmap[fc_i] = -1;
		fc_thischarcnt = fc_stdcnt = fc_utcnt = 0;
		for (fc_i = fc_old0; fc_i < typecnt; fc_i++)
		{
			char	   *fc_thisabbr;

			if (fc_omittype[fc_i])
				continue;
			if (ttisstds[fc_i])
				fc_stdcnt = fc_thistypecnt;
			if (ttisuts[fc_i])
				fc_utcnt = fc_thistypecnt;
			if (fc_indmap[desigidx[fc_i]] >= 0)
				continue;
			fc_thisabbr = &chars[desigidx[fc_i]];
			for (fc_j = 0; fc_j < fc_thischarcnt; ++fc_j)
				if (strcmp(&fc_thischars[fc_j], fc_thisabbr) == 0)
					break;
			if (fc_j == fc_thischarcnt)
			{
				strcpy(&fc_thischars[fc_thischarcnt], fc_thisabbr);
				fc_thischarcnt += strlen(fc_thisabbr) + 1;
			}
			fc_indmap[desigidx[fc_i]] = fc_j;
		}
		if (fc_pass == 1 && !fc_want_bloat())
		{
			fc_utcnt = fc_stdcnt = fc_thisleapcnt = 0;
			fc_thistimecnt = -(fc_locut + fc_hicut);
			fc_thistypecnt = fc_thischarcnt = 1;
			fc_thistimelim = fc_thistimei;
		}
#define DO(field)	fwrite(fc_tzh.field, sizeof fc_tzh.field, 1, fc_fp)
		fc_tzh = fc_tzh0;
		memcpy(fc_tzh.tzh_magic, TZ_MAGIC, sizeof fc_tzh.tzh_magic);
		fc_tzh.tzh_version[0] = fc_version;
		fc_convert(fc_utcnt, fc_tzh.tzh_ttisutcnt);
		fc_convert(fc_stdcnt, fc_tzh.tzh_ttisstdcnt);
		fc_convert(fc_thisleapcnt, fc_tzh.tzh_leapcnt);
		fc_convert(fc_locut + fc_thistimecnt + fc_hicut, fc_tzh.tzh_timecnt);
		fc_convert(fc_thistypecnt, fc_tzh.tzh_typecnt);
		fc_convert(fc_thischarcnt, fc_tzh.tzh_charcnt);
		DO(tzh_magic);
		DO(tzh_version);
		DO(tzh_reserved);
		DO(tzh_ttisutcnt);
		DO(tzh_ttisstdcnt);
		DO(tzh_leapcnt);
		DO(tzh_timecnt);
		DO(tzh_typecnt);
		DO(tzh_charcnt);
#undef DO
		if (fc_pass == 1 && !fc_want_bloat())
		{
			/* 输出一个仅包含一种时间类型的最小数据块。 */
			fc_puttzcode(0, fc_fp);	/* utoff */
			putc(0, fc_fp);		/* dst */
			putc(0, fc_fp);		/* 缩写索引 */
			putc(0, fc_fp);		/* 空字符串缩写 */
			continue;
		}

		/* PG：如果请求，打印当前时区缩写 */
		if (print_abbrevs && fc_pass == 2)
		{
			/* 打印在 print_cutoff 之后结束的“类型”数据 */
			for (fc_i = fc_thistimei; fc_i < fc_thistimelim; ++fc_i)
			{
				if (fc_i == fc_thistimelim - 1 || fc_ats[fc_i + 1] > print_cutoff)
				{
					unsigned char fc_tm = fc_types[fc_i];
					char	   *fc_thisabbrev = &fc_thischars[fc_indmap[desigidx[fc_tm]]];

					fprintf(stdout, "%s\t" INT64_FORMAT "%s\n",
							fc_thisabbrev,
							utoffs[fc_tm],
							isdsts[fc_tm] ? "\tD" : "");
				}
			}
			/* 如果我们根本没有过渡，则打印默认类型 */
			if (fc_thistimei >= fc_thistimelim)
			{
				unsigned char fc_tm = fc_defaulttype;
				char	   *fc_thisabbrev = &fc_thischars[fc_indmap[desigidx[fc_tm]]];

				fprintf(stdout, "%s\t" INT64_FORMAT "%s\n",
						fc_thisabbrev,
						utoffs[fc_tm],
						isdsts[fc_tm] ? "\tD" : "");
			}
		}

		/*
		 * 如果需要，输出一个 LO_TIME 过渡；参见 limitrange。
		 * 但不要在此次处理中低于可表示的最小值。
		 */
		fc_lo = fc_pass == 1 && lo_time < PG_INT32_MIN ? PG_INT32_MIN : lo_time;

		if (fc_locut)
			fc_puttzcodepass(fc_lo, fc_fp, fc_pass);
		for (fc_i = fc_thistimei; fc_i < fc_thistimelim; ++fc_i)
		{
			zic_t		fc_at = fc_ats[fc_i] < fc_lo ? fc_lo : fc_ats[fc_i];

			fc_puttzcodepass(fc_at, fc_fp, fc_pass);
		}
		if (fc_hicut)
			fc_puttzcodepass(hi_time + 1, fc_fp, fc_pass);
		fc_currenttype = 0;
		if (fc_locut)
			putc(fc_currenttype, fc_fp);
		for (fc_i = fc_thistimei; fc_i < fc_thistimelim; ++fc_i)
		{
			fc_currenttype = fc_typemap[fc_types[fc_i]];
			putc(fc_currenttype, fc_fp);
		}
		if (fc_hicut)
			putc(fc_currenttype, fc_fp);

		for (fc_i = fc_old0; fc_i < typecnt; fc_i++)
		{
			int			fc_h = (fc_i == fc_old0 ? fc_thisdefaulttype
							 : fc_i == fc_thisdefaulttype ? fc_old0 : fc_i);

			if (!fc_omittype[fc_h])
			{
				fc_puttzcode(utoffs[fc_h], fc_fp);
				putc(isdsts[fc_h], fc_fp);
				putc(fc_indmap[desigidx[fc_h]], fc_fp);
			}
		}
		if (fc_thischarcnt != 0)
			fwrite(fc_thischars, sizeof fc_thischars[0],
				   fc_thischarcnt, fc_fp);
		for (fc_i = fc_thisleapi; fc_i < fc_thisleaplim; ++fc_i)
		{
			zic_t		fc_todo;

			if (roll[fc_i])
			{
				if (timecnt == 0 || trans[fc_i] < fc_ats[0])
				{
					fc_j = 0;
					while (isdsts[fc_j])
						if (++fc_j >= typecnt)
						{
							fc_j = 0;
							break;
						}
				}
				else
				{
					fc_j = 1;
					while (fc_j < timecnt &&
						   trans[fc_i] >= fc_ats[fc_j])
						++fc_j;
					fc_j = fc_types[fc_j - 1];
				}
				fc_todo = fc_tadd(trans[fc_i], -utoffs[fc_j]);
			}
			else
				fc_todo = trans[fc_i];
			fc_puttzcodepass(fc_todo, fc_fp, fc_pass);
			fc_puttzcode(corr[fc_i], fc_fp);
		}
		if (fc_stdcnt != 0)
			for (fc_i = fc_old0; fc_i < typecnt; fc_i++)
				if (!fc_omittype[fc_i])
					putc(ttisstds[fc_i], fc_fp);
		if (fc_utcnt != 0)
			for (fc_i = fc_old0; fc_i < typecnt; fc_i++)
				if (!fc_omittype[fc_i])
					putc(ttisuts[fc_i], fc_fp);
	}
	fprintf(fc_fp, "\n%s\n", fc_string);
	fc_close_file(fc_fp, directory, fc_name);
	free(fc_ats);
}

static char const *
fc_abbroffset(char *fc_buf, zic_t fc_offset)
{
	char		fc_sign = '+';
	int			fc_seconds,
				fc_minutes;

	if (fc_offset < 0)
	{
		fc_offset = -fc_offset;
		fc_sign = '-';
	}

	fc_seconds = fc_offset % SECSPERMIN;
	fc_offset /= SECSPERMIN;
	fc_minutes = fc_offset % MINSPERHOUR;
	fc_offset /= MINSPERHOUR;
	if (100 <= fc_offset)
	{
		fc_error(_("%%z UT offset magnitude exceeds 99:59:59"));
		return "%z";
	}
	else
	{
		char	   *fc_p = fc_buf;

		*fc_p++ = fc_sign;
		*fc_p++ = '0' + fc_offset / 10;
		*fc_p++ = '0' + fc_offset % 10;
		if (fc_minutes | fc_seconds)
		{
			*fc_p++ = '0' + fc_minutes / 10;
			*fc_p++ = '0' + fc_minutes % 10;
			if (fc_seconds)
			{
				*fc_p++ = '0' + fc_seconds / 10;
				*fc_p++ = '0' + fc_seconds % 10;
			}
		}
		*fc_p = '\0';
		return fc_buf;
	}
}

static size_t fc_doabbr(char *fc_abbr, struct zone const *fc_zp, char const *fc_letters,
	   bool fc_isdst, zic_t fc_save, bool fc_doquotes)
{
	char	   *fc_cp;
	char	   *fc_slashp;
	size_t		fc_len;
	char const *fc_format = fc_zp->z_format;

	fc_slashp = strchr(fc_format, '/');
	if (fc_slashp == NULL)
	{
		char		fc_letterbuf[PERCENT_Z_LEN_BOUND + 1];

		if (fc_zp->z_format_specifier == 'z')
			fc_letters = fc_abbroffset(fc_letterbuf, fc_zp->z_stdoff + fc_save);
		else if (!fc_letters)
			fc_letters = "%s";
		sprintf(fc_abbr, fc_format, fc_letters);
	}
	else if (fc_isdst)
	{
		strcpy(fc_abbr, fc_slashp + 1);
	}
	else
	{
		memcpy(fc_abbr, fc_format, fc_slashp - fc_format);
		fc_abbr[fc_slashp - fc_format] = '\0';
	}
	fc_len = strlen(fc_abbr);
	if (!fc_doquotes)
		return fc_len;
	for (fc_cp = fc_abbr; fc_is_alpha(*fc_cp); fc_cp++)
		continue;
	if (fc_len > 0 && *fc_cp == '\0')
		return fc_len;
	fc_abbr[fc_len + 2] = '\0';
	fc_abbr[fc_len + 1] = '>';
	memmove(fc_abbr + 1, fc_abbr, fc_len);
	fc_abbr[0] = '<';
	return fc_len + 2;
}

static void fc_updateminmax(const zic_t fc_x)
{
	if (min_year > fc_x)
		min_year = fc_x;
	if (max_year < fc_x)
		max_year = fc_x;
}

static int fc_stringoffset(char *fc_result, zic_t fc_offset)
{
	int			fc_hours;
	int			fc_minutes;
	int			fc_seconds;
	bool		fc_negative = fc_offset < 0;
	int			fc_len = fc_negative;

	if (fc_negative)
	{
		fc_offset = -fc_offset;
		fc_result[0] = '-';
	}
	fc_seconds = fc_offset % SECSPERMIN;
	fc_offset /= SECSPERMIN;
	fc_minutes = fc_offset % MINSPERHOUR;
	fc_offset /= MINSPERHOUR;
	fc_hours = fc_offset;
	if (fc_hours >= HOURSPERDAY * DAYSPERWEEK)
	{
		fc_result[0] = '\0';
		return 0;
	}
	fc_len += sprintf(fc_result + fc_len, "%d", fc_hours);
	if (fc_minutes != 0 || fc_seconds != 0)
	{
		fc_len += sprintf(fc_result + fc_len, ":%02d", fc_minutes);
		if (fc_seconds != 0)
			fc_len += sprintf(fc_result + fc_len, ":%02d", fc_seconds);
	}
	return fc_len;
}

static int fc_stringrule(char *fc_result, struct rule *const fc_rp, zic_t fc_save, zic_t fc_stdoff)
{
	zic_t		fc_tod = fc_rp->r_tod;
	int			fc_compat = 0;

	if (fc_rp->r_dycode == DC_DOM)
	{
		int			fc_month,
					fc_total;

		if (fc_rp->r_dayofmonth == 29 && fc_rp->r_month == TM_FEBRUARY)
			return -1;
		fc_total = 0;
		for (fc_month = 0; fc_month < fc_rp->r_month; ++fc_month)
			fc_total += len_months[0][fc_month];
		/* 省略 "J" 在 1 月和 2 月，因为这样更短。 */
		if (fc_rp->r_month <= 1)
			fc_result += sprintf(fc_result, "%d", fc_total + fc_rp->r_dayofmonth - 1);
		else
			fc_result += sprintf(fc_result, "J%d", fc_total + fc_rp->r_dayofmonth);
	}
	else
	{
		int			fc_week;
		int			fc_wday = fc_rp->r_wday;
		int			fc_wdayoff;

		if (fc_rp->r_dycode == DC_DOWGEQ)
		{
			fc_wdayoff = (fc_rp->r_dayofmonth - 1) % DAYSPERWEEK;
			if (fc_wdayoff)
				fc_compat = 2013;
			fc_wday -= fc_wdayoff;
			fc_tod += fc_wdayoff * SECSPERDAY;
			fc_week = 1 + (fc_rp->r_dayofmonth - 1) / DAYSPERWEEK;
		}
		else if (fc_rp->r_dycode == DC_DOWLEQ)
		{
			if (fc_rp->r_dayofmonth == len_months[1][fc_rp->r_month])
				fc_week = 5;
			else
			{
				fc_wdayoff = fc_rp->r_dayofmonth % DAYSPERWEEK;
				if (fc_wdayoff)
					fc_compat = 2013;
				fc_wday -= fc_wdayoff;
				fc_tod += fc_wdayoff * SECSPERDAY;
				fc_week = fc_rp->r_dayofmonth / DAYSPERWEEK;
			}
		}
		else
			return -1;			/* "无法发生" */
		if (fc_wday < 0)
			fc_wday += DAYSPERWEEK;
		fc_result += sprintf(fc_result, "M%d.%d.%d",
						  fc_rp->r_month + 1, fc_week, fc_wday);
	}
	if (fc_rp->r_todisut)
		fc_tod += fc_stdoff;
	if (fc_rp->r_todisstd && !fc_rp->r_isdst)
		fc_tod += fc_save;
	if (fc_tod != 2 * SECSPERMIN * MINSPERHOUR)
	{
		*fc_result++ = '/';
		if (!fc_stringoffset(fc_result, fc_tod))
			return -1;
		if (fc_tod < 0)
		{
			if (fc_compat < 2013)
				fc_compat = 2013;
		}
		else if (SECSPERDAY <= fc_tod)
		{
			if (fc_compat < 1994)
				fc_compat = 1994;
		}
	}
	return fc_compat;
}

static int fc_rule_cmp(struct rule const *fc_a, struct rule const *fc_b)
{
	if (!fc_a)
		return -!!fc_b;
	if (!fc_b)
		return 1;
	if (fc_a->r_hiyear != fc_b->r_hiyear)
		return fc_a->r_hiyear < fc_b->r_hiyear ? -1 : 1;
	if (fc_a->r_month - fc_b->r_month != 0)
		return fc_a->r_month - fc_b->r_month;
	return fc_a->r_dayofmonth - fc_b->r_dayofmonth;
}

static int fc_stringzone(char *fc_result, struct zone const *fc_zpfirst, ptrdiff_t fc_zonecount)
{
	const struct zone *fc_zp;
	struct rule *fc_rp;
	struct rule *fc_stdrp;
	struct rule *fc_dstrp;
	ptrdiff_t	fc_i;
	const char *fc_abbrvar;
	int			fc_compat = 0;
	int			fc_c;
	size_t		fc_len;
	int			fc_offsetlen;
	struct rule fc_stdr,
				fc_dstr;

	fc_result[0] = '\0';

	/*
	 * 互联网 RFC 8536 第 5.1 部分说，如果未来时间戳
	 * 被截断，应使用空的 TZ 字符串。
	 */
	if (hi_time < max_time)
		return -1;

	fc_zp = fc_zpfirst + fc_zonecount - 1;
	fc_stdrp = fc_dstrp = NULL;
	for (fc_i = 0; fc_i < fc_zp->z_nrules; ++fc_i)
	{
		fc_rp = &fc_zp->z_rules[fc_i];
		if (fc_rp->r_hiwasnum || fc_rp->r_hiyear != ZIC_MAX)
			continue;
		if (!fc_rp->r_isdst)
		{
			if (fc_stdrp == NULL)
				fc_stdrp = fc_rp;
			else
				return -1;
		}
		else
		{
			if (fc_dstrp == NULL)
				fc_dstrp = fc_rp;
			else
				return -1;
		}
	}
	if (fc_stdrp == NULL && fc_dstrp == NULL)
	{
		/*
		 * “max”中没有规则。查找stdabbrrp中的最新标准规则
		 * 和stdrp中的最新任何类型的规则。
		 */
		struct rule *fc_stdabbrrp = NULL;

		for (fc_i = 0; fc_i < fc_zp->z_nrules; ++fc_i)
		{
			fc_rp = &fc_zp->z_rules[fc_i];
			if (!fc_rp->r_isdst && fc_rule_cmp(fc_stdabbrrp, fc_rp) < 0)
				fc_stdabbrrp = fc_rp;
			if (fc_rule_cmp(fc_stdrp, fc_rp) < 0)
				fc_stdrp = fc_rp;
		}
		if (fc_stdrp != NULL && fc_stdrp->r_isdst)
		{
			/* 永久夏令时。 */
			fc_dstr.r_month = TM_JANUARY;
			fc_dstr.r_dycode = DC_DOM;
			fc_dstr.r_dayofmonth = 1;
			fc_dstr.r_tod = 0;
			fc_dstr.r_todisstd = fc_dstr.r_todisut = false;
			fc_dstr.r_isdst = fc_stdrp->r_isdst;
			fc_dstr.r_save = fc_stdrp->r_save;
			fc_dstr.r_abbrvar = fc_stdrp->r_abbrvar;
			fc_stdr.r_month = TM_DECEMBER;
			fc_stdr.r_dycode = DC_DOM;
			fc_stdr.r_dayofmonth = 31;
			fc_stdr.r_tod = SECSPERDAY + fc_stdrp->r_save;
			fc_stdr.r_todisstd = fc_stdr.r_todisut = false;
			fc_stdr.r_isdst = false;
			fc_stdr.r_save = 0;
			fc_stdr.r_abbrvar
				= (fc_stdabbrrp ? fc_stdabbrrp->r_abbrvar : "");
			fc_dstrp = &fc_dstr;
			fc_stdrp = &fc_stdr;
		}
	}
	if (fc_stdrp == NULL && (fc_zp->z_nrules != 0 || fc_zp->z_isdst))
		return -1;
	fc_abbrvar = (fc_stdrp == NULL) ? "" : fc_stdrp->r_abbrvar;
	fc_len = fc_doabbr(fc_result, fc_zp, fc_abbrvar, false, 0, true);
	fc_offsetlen = fc_stringoffset(fc_result + fc_len, -fc_zp->z_stdoff);
	if (!fc_offsetlen)
	{
		fc_result[0] = '\0';
		return -1;
	}
	fc_len += fc_offsetlen;
	if (fc_dstrp == NULL)
		return fc_compat;
	fc_len += fc_doabbr(fc_result + fc_len, fc_zp, fc_dstrp->r_abbrvar,
				  fc_dstrp->r_isdst, fc_dstrp->r_save, true);
	if (fc_dstrp->r_save != SECSPERMIN * MINSPERHOUR)
	{
		fc_offsetlen = fc_stringoffset(fc_result + fc_len,
								 -(fc_zp->z_stdoff + fc_dstrp->r_save));
		if (!fc_offsetlen)
		{
			fc_result[0] = '\0';
			return -1;
		}
		fc_len += fc_offsetlen;
	}
	fc_result[fc_len++] = ',';
	fc_c = fc_stringrule(fc_result + fc_len, fc_dstrp, fc_dstrp->r_save, fc_zp->z_stdoff);
	if (fc_c < 0)
	{
		fc_result[0] = '\0';
		return -1;
	}
	if (fc_compat < fc_c)
		fc_compat = fc_c;
	fc_len += strlen(fc_result + fc_len);
	fc_result[fc_len++] = ',';
	fc_c = fc_stringrule(fc_result + fc_len, fc_stdrp, fc_dstrp->r_save, fc_zp->z_stdoff);
	if (fc_c < 0)
	{
		fc_result[0] = '\0';
		return -1;
	}
	if (fc_compat < fc_c)
		fc_compat = fc_c;
	return fc_compat;
}

static void fc_outzone(const struct zone *fc_zpfirst, ptrdiff_t fc_zonecount)
{
	const struct zone *fc_zp;
	struct rule *fc_rp;
	ptrdiff_t	fc_i,
				fc_j;
	bool		fc_usestart,
				fc_useuntil;
	zic_t		fc_starttime,
				fc_untiltime;
	zic_t		fc_stdoff;
	zic_t		fc_save;
	zic_t		fc_year;
	zic_t		fc_startoff;
	bool		fc_startttisstd;
	bool		fc_startttisut;
	int			fc_type;
	char	   *fc_startbuf;
	char	   *fc_ab;
	char	   *fc_envvar;
	int			fc_max_abbr_len;
	int			fc_max_envvar_len;
	bool		fc_prodstic;		/* 所有规则都是从最小到最大 */
	int			fc_compat;
	bool		fc_do_extend;
	char		fc_version;
	ptrdiff_t	fc_lastatmax = -1;
	zic_t		fc_one = 1;
	zic_t		fc_y2038_boundary = fc_one << 31;
	zic_t		fc_max_year0;
	int			fc_defaulttype = -1;

	fc_max_abbr_len = 2 + max_format_len + max_abbrvar_len;
	fc_max_envvar_len = 2 * fc_max_abbr_len + 5 * 9;
	fc_startbuf = fc_emalloc(fc_max_abbr_len + 1);
	fc_ab = fc_emalloc(fc_max_abbr_len + 1);
	fc_envvar = fc_emalloc(fc_max_envvar_len + 1);
	INITIALIZE(fc_untiltime);
	INITIALIZE(fc_starttime);

	/*
	 * 现在……终于……生成一些有用的数据！
	 */
	timecnt = 0;
	typecnt = 0;
	charcnt = 0;
	fc_prodstic = fc_zonecount == 1;

	/*
	 * 感谢Earl Chew指出需要无条件初始化
	 * startttisstd。
	 */
	fc_startttisstd = false;
	fc_startttisut = false;
	min_year = max_year = EPOCH_YEAR;
	if (leapseen)
	{
		fc_updateminmax(leapminyear);
		fc_updateminmax(leapmaxyear + (leapmaxyear < ZIC_MAX));
	}
	for (fc_i = 0; fc_i < fc_zonecount; ++fc_i)
	{
		fc_zp = &fc_zpfirst[fc_i];
		if (fc_i < fc_zonecount - 1)
			fc_updateminmax(fc_zp->z_untilrule.r_loyear);
		for (fc_j = 0; fc_j < fc_zp->z_nrules; ++fc_j)
		{
			fc_rp = &fc_zp->z_rules[fc_j];
			if (fc_rp->r_lowasnum)
				fc_updateminmax(fc_rp->r_loyear);
			if (fc_rp->r_hiwasnum)
				fc_updateminmax(fc_rp->r_hiyear);
			if (fc_rp->r_lowasnum || fc_rp->r_hiwasnum)
				fc_prodstic = false;
		}
	}

	/*
	 * 如果规则不能覆盖所有未来时间，生成大量数据。
	 */
	fc_compat = fc_stringzone(fc_envvar, fc_zpfirst, fc_zonecount);
	fc_version = fc_compat < 2013 ? ZIC_VERSION_PRE_2013 : ZIC_VERSION;
	fc_do_extend = fc_compat < 0;
	if (noise)
	{
		if (!*fc_envvar)
			fc_warning("%s %s",
					_("no POSIX environment variable for zone"),
					fc_zpfirst->z_name);
		else if (fc_compat != 0)
		{
			/*
			 * 早期的COMPAT客户端和较早的客户端在给定1970年之前或2038年之后的日期时可能无法
			 * 在该时区工作。
			 */
			fc_warning(_("%s: pre-%d clients may mishandle"
					  " distant timestamps"),
					fc_zpfirst->z_name, fc_compat);
		}
	}
	if (fc_do_extend)
	{
		/*
		 * 在明显的400年之外再搜索几个额外的年份，以
		 * 避免边缘案例。例如，假设一个非POSIX规则适用于
		 * 从2012年开始，并在3月和9月有转换，以及
		 * 在2013年11月有一些一次性转换。如果zic仅关注
		 * 最近的400年，它将设置max_year=2413，意图
		 * 是2014年至2413年的400年将会被重复。tzfile中列出的最后
		 * 个转换将在2413-09，距离2013-11的最后一次性
		 * 转换不到400年。两年的时间可能太多，但考虑到
		 * 可用的边缘案例，我们不确定一年是否足够。
		 */
		enum
		{
		years_of_observations = YEARSPERREPEAT + 2};

		if (min_year >= ZIC_MIN + years_of_observations)
			min_year -= years_of_observations;
		else
			min_year = ZIC_MIN;
		if (max_year <= ZIC_MAX - years_of_observations)
			max_year += years_of_observations;
		else
			max_year = ZIC_MAX;

		/*
		 * 不论上述任何情况，对于一个“proDSTic”时区，该时区
		 * 指定其规则始终已生效且将始终生效，
		 * 我们只需要一个周期来定义该时区。
		 */
		if (fc_prodstic)
		{
			min_year = 1900;
			max_year = min_year + years_of_observations;
		}
	}
	fc_max_year0 = max_year;
	if (fc_want_bloat())
	{
		/*
		 * 为了老系统的利益，从1900年生成到2038年的数据。
		 */
		if (min_year > 1900)
			min_year = 1900;
		if (max_year < 2038)
			max_year = 2038;
	}

	for (fc_i = 0; fc_i < fc_zonecount; ++fc_i)
	{
		struct rule *fc_prevrp = NULL;

		/*
		 * 可能需要稍后更正的猜测。
		 */
		fc_save = 0;
		fc_zp = &fc_zpfirst[fc_i];
		fc_usestart = fc_i > 0 && (fc_zp - 1)->z_untiltime > min_time;
		fc_useuntil = fc_i < (fc_zonecount - 1);
		if (fc_useuntil && fc_zp->z_untiltime <= min_time)
			continue;
		fc_stdoff = fc_zp->z_stdoff;
		fc_eat(fc_zp->z_filename, fc_zp->z_linenum);
		*fc_startbuf = '\0';
		fc_startoff = fc_zp->z_stdoff;
		if (fc_zp->z_nrules == 0)
		{
			fc_save = fc_zp->z_save;
			fc_doabbr(fc_startbuf, fc_zp, NULL, fc_zp->z_isdst, fc_save, false);
			fc_type = fc_addtype(fc_oadd(fc_zp->z_stdoff, fc_save),
						   fc_startbuf, fc_zp->z_isdst, fc_startttisstd,
						   fc_startttisut);
			if (fc_usestart)
			{
				fc_addtt(fc_starttime, fc_type);
				fc_usestart = false;
			}
			else
				fc_defaulttype = fc_type;
		}
		else
			for (fc_year = min_year; fc_year <= max_year; ++fc_year)
			{
				if (fc_useuntil && fc_year > fc_zp->z_untilrule.r_hiyear)
					break;

				/*
				 * 标记当前年份要执行哪些规则。对于需要执行的规则，
				 * 计算rpytime(rp, year)；之前的TYPE字段在
				 * 这里也考虑在内。
				 */
				for (fc_j = 0; fc_j < fc_zp->z_nrules; ++fc_j)
				{
					fc_rp = &fc_zp->z_rules[fc_j];
					fc_eats(fc_zp->z_filename, fc_zp->z_linenum,
						 fc_rp->r_filename, fc_rp->r_linenum);
					fc_rp->r_todo = fc_year >= fc_rp->r_loyear &&
						fc_year <= fc_rp->r_hiyear;
					if (fc_rp->r_todo)
					{
						fc_rp->r_temp = fc_rpytime(fc_rp, fc_year);
						fc_rp->r_todo
							= (fc_rp->r_temp < fc_y2038_boundary
							   || fc_year <= fc_max_year0);
					}
				}
				for (;;)
				{
					ptrdiff_t	fc_k;
					zic_t		fc_jtime,
								fc_ktime;
					zic_t		fc_offset;

					INITIALIZE(fc_ktime);
					if (fc_useuntil)
					{
						/*
						 * 将untiltime转换为UT，假设当前的stdoff
						 * 并保存值。
						 */
						fc_untiltime = fc_zp->z_untiltime;
						if (!fc_zp->z_untilrule.r_todisut)
							fc_untiltime = fc_tadd(fc_untiltime,
											 -fc_stdoff);
						if (!fc_zp->z_untilrule.r_todisstd)
							fc_untiltime = fc_tadd(fc_untiltime,
											 -fc_save);
					}

					/*
					 * 找到 (要执行的，如果有的话) 在年度中生效最早的规则。
					 */
					fc_k = -1;
					for (fc_j = 0; fc_j < fc_zp->z_nrules; ++fc_j)
					{
						fc_rp = &fc_zp->z_rules[fc_j];
						if (!fc_rp->r_todo)
							continue;
						fc_eats(fc_zp->z_filename, fc_zp->z_linenum,
							 fc_rp->r_filename, fc_rp->r_linenum);
						fc_offset = fc_rp->r_todisut ? 0 : fc_stdoff;
						if (!fc_rp->r_todisstd)
							fc_offset = fc_oadd(fc_offset, fc_save);
						fc_jtime = fc_rp->r_temp;
						if (fc_jtime == min_time ||
							fc_jtime == max_time)
							continue;
						fc_jtime = fc_tadd(fc_jtime, -fc_offset);
						if (fc_k < 0 || fc_jtime < fc_ktime)
						{
							fc_k = fc_j;
							fc_ktime = fc_jtime;
						}
						else if (fc_jtime == fc_ktime)
						{
							char const *fc_dup_rules_msg =
							_("two rules for same instant");

							fc_eats(fc_zp->z_filename, fc_zp->z_linenum,
								 fc_rp->r_filename, fc_rp->r_linenum);
							fc_warning("%s", fc_dup_rules_msg);
							fc_rp = &fc_zp->z_rules[fc_k];
							fc_eats(fc_zp->z_filename, fc_zp->z_linenum,
								 fc_rp->r_filename, fc_rp->r_linenum);
							fc_error("%s", fc_dup_rules_msg);
						}
					}
					if (fc_k < 0)
						break;	/* 继续下一年 */
					fc_rp = &fc_zp->z_rules[fc_k];
					fc_rp->r_todo = false;
					if (fc_useuntil && fc_ktime >= fc_untiltime)
						break;
					fc_save = fc_rp->r_save;
					if (fc_usestart && fc_ktime == fc_starttime)
						fc_usestart = false;
					if (fc_usestart)
					{
						if (fc_ktime < fc_starttime)
						{
							fc_startoff = fc_oadd(fc_zp->z_stdoff,
											fc_save);
							fc_doabbr(fc_startbuf, fc_zp,
								   fc_rp->r_abbrvar,
								   fc_rp->r_isdst,
								   fc_rp->r_save,
								   false);
							continue;
						}
						if (*fc_startbuf == '\0'
							&& fc_startoff == fc_oadd(fc_zp->z_stdoff,
												fc_save))
						{
							fc_doabbr(fc_startbuf,
								   fc_zp,
								   fc_rp->r_abbrvar,
								   fc_rp->r_isdst,
								   fc_rp->r_save,
								   false);
						}
					}
					fc_eats(fc_zp->z_filename, fc_zp->z_linenum,
						 fc_rp->r_filename, fc_rp->r_linenum);
					fc_doabbr(fc_ab, fc_zp, fc_rp->r_abbrvar,
						   fc_rp->r_isdst, fc_rp->r_save, false);
					fc_offset = fc_oadd(fc_zp->z_stdoff, fc_rp->r_save);
					if (!fc_want_bloat() && !fc_useuntil && !fc_do_extend
						&& fc_prevrp
						&& fc_rp->r_hiyear == ZIC_MAX
						&& fc_prevrp->r_hiyear == ZIC_MAX)
						break;
					fc_type = fc_addtype(fc_offset, fc_ab, fc_rp->r_isdst,
								   fc_rp->r_todisstd, fc_rp->r_todisut);
					if (fc_defaulttype < 0 && !fc_rp->r_isdst)
						fc_defaulttype = fc_type;
					if (fc_rp->r_hiyear == ZIC_MAX
						&& !(0 <= fc_lastatmax
							 && fc_ktime < attypes[fc_lastatmax].at))
						fc_lastatmax = timecnt;
					fc_addtt(fc_ktime, fc_type);
					fc_prevrp = fc_rp;
				}
			}
		if (fc_usestart)
		{
			if (*fc_startbuf == '\0' &&
				fc_zp->z_format != NULL &&
				strchr(fc_zp->z_format, '%') == NULL &&
				strchr(fc_zp->z_format, '/') == NULL)
				strcpy(fc_startbuf, fc_zp->z_format);
			fc_eat(fc_zp->z_filename, fc_zp->z_linenum);
			if (*fc_startbuf == '\0')
				fc_error(_("cannot determine time zone abbreviation to use just after until time"));
			else
			{
				bool		fc_isdst = fc_startoff != fc_zp->z_stdoff;

				fc_type = fc_addtype(fc_startoff, fc_startbuf, fc_isdst,
							   fc_startttisstd, fc_startttisut);
				if (fc_defaulttype < 0 && !fc_isdst)
					fc_defaulttype = fc_type;
				fc_addtt(fc_starttime, fc_type);
			}
		}

		/*
		 * 现在我们可以为下一条时区线设置starttime。
		 */
		if (fc_useuntil)
		{
			fc_startttisstd = fc_zp->z_untilrule.r_todisstd;
			fc_startttisut = fc_zp->z_untilrule.r_todisut;
			fc_starttime = fc_zp->z_untiltime;
			if (!fc_startttisstd)
				fc_starttime = fc_tadd(fc_starttime, -fc_save);
			if (!fc_startttisut)
				fc_starttime = fc_tadd(fc_starttime, -fc_stdoff);
		}
	}
	if (fc_defaulttype < 0)
		fc_defaulttype = 0;
	if (0 <= fc_lastatmax)
		attypes[fc_lastatmax].dontmerge = true;
	if (fc_do_extend)
	{
		/*
		 * 如果我们因为无法填充POSIX-TZ字段而在400年内延长显式列出的观测值，请检查
		 * 我们是否实际上确实结束时显式列出了这个时期的观测值。如果在
		 * 400年期间的末尾附近没有任何数据，请在最后一年末添加一个
		 * 冗余值，以明确我们声称对这一点缺乏
		 * 转换有明确的认识。
		 */
		struct rule fc_xr;
		struct attype *fc_lastat;

		fc_xr.r_month = TM_JANUARY;
		fc_xr.r_dycode = DC_DOM;
		fc_xr.r_dayofmonth = 1;
		fc_xr.r_tod = 0;
		for (fc_lastat = attypes, fc_i = 1; fc_i < timecnt; fc_i++)
			if (attypes[fc_i].at > fc_lastat->at)
				fc_lastat = &attypes[fc_i];
		if (!fc_lastat || fc_lastat->at < fc_rpytime(&fc_xr, max_year - 1))
		{
			fc_addtt(fc_rpytime(&fc_xr, max_year + 1),
				  fc_lastat ? fc_lastat->type : fc_defaulttype);
			attypes[timecnt - 1].dontmerge = true;
		}
	}
	fc_writezone(fc_zpfirst->z_name, fc_envvar, fc_version, fc_defaulttype);
	free(fc_startbuf);
	free(fc_ab);
	free(fc_envvar);
}

static void fc_addtt(zic_t fc_starttime, int fc_type)
{
	attypes = fc_growalloc(attypes, sizeof *attypes, timecnt, &timecnt_alloc);
	attypes[timecnt].at = fc_starttime;
	attypes[timecnt].dontmerge = false;
	attypes[timecnt].type = fc_type;
	++timecnt;
}

static int fc_addtype(zic_t fc_utoff, char const *fc_abbr, bool fc_isdst, bool fc_ttisstd, bool fc_ttisut)
{
	int			fc_i,
				fc_j;

	if (!(-1L - 2147483647L <= fc_utoff && fc_utoff <= 2147483647L))
	{
		fc_error(_("UT offset out of range"));
		exit(EXIT_FAILURE);
	}
	if (!fc_want_bloat())
		fc_ttisstd = fc_ttisut = false;

	for (fc_j = 0; fc_j < charcnt; ++fc_j)
		if (strcmp(&chars[fc_j], fc_abbr) == 0)
			break;
	if (fc_j == charcnt)
		fc_newabbr(fc_abbr);
	else
	{
		/* 如果已经有一个条目，返回其索引。 */
		for (fc_i = 0; fc_i < typecnt; fc_i++)
			if (fc_utoff == utoffs[fc_i] && fc_isdst == isdsts[fc_i] && fc_j == desigidx[fc_i]
				&& fc_ttisstd == ttisstds[fc_i] && fc_ttisut == ttisuts[fc_i])
				return fc_i;
	}

	/*
	 * 没有一个；添加一个新条目，除非已经有太多。
	 */
	if (typecnt >= TZ_MAX_TYPES)
	{
		fc_error(_("too many local time types"));
		exit(EXIT_FAILURE);
	}
	fc_i = typecnt++;
	utoffs[fc_i] = fc_utoff;
	isdsts[fc_i] = fc_isdst;
	ttisstds[fc_i] = fc_ttisstd;
	ttisuts[fc_i] = fc_ttisut;
	desigidx[fc_i] = fc_j;
	return fc_i;
}

static void fc_leapadd(zic_t fc_t, int fc_correction, int fc_rolling)
{
	int			fc_i;

	if (TZ_MAX_LEAPS <= leapcnt)
	{
		fc_error(_("too many leap seconds"));
		exit(EXIT_FAILURE);
	}
	for (fc_i = 0; fc_i < leapcnt; ++fc_i)
		if (fc_t <= trans[fc_i])
			break;
	memmove(&trans[fc_i + 1], &trans[fc_i], (leapcnt - fc_i) * sizeof *trans);
	memmove(&corr[fc_i + 1], &corr[fc_i], (leapcnt - fc_i) * sizeof *corr);
	memmove(&roll[fc_i + 1], &roll[fc_i], (leapcnt - fc_i) * sizeof *roll);
	trans[fc_i] = fc_t;
	corr[fc_i] = fc_correction;
	roll[fc_i] = fc_rolling;
	++leapcnt;
}

static void fc_adjleap(void)
{
	int			fc_i;
	zic_t		fc_last = 0;
	zic_t		fc_prevtrans = 0;

	/*
	 * 向前传播闰秒
	 */
	for (fc_i = 0; fc_i < leapcnt; ++fc_i)
	{
		if (trans[fc_i] - fc_prevtrans < 28 * SECSPERDAY)
		{
			fc_error(_("Leap seconds too close together"));
			exit(EXIT_FAILURE);
		}
		fc_prevtrans = trans[fc_i];
		trans[fc_i] = fc_tadd(trans[fc_i], fc_last);
		fc_last = corr[fc_i] += fc_last;
	}

	if (leapexpires < 0)
	{
		leapexpires = comment_leapexpires;
		if (0 <= leapexpires)
			fc_warning(_("\"#expires\" is obsolescent; use \"Expires\""));
	}

	if (0 <= leapexpires)
	{
		leapexpires = fc_oadd(leapexpires, fc_last);
		if (!(leapcnt == 0 || (trans[leapcnt - 1] < leapexpires)))
		{
			fc_error(_("last Leap time does not precede Expires time"));
			exit(EXIT_FAILURE);
		}
		if (leapexpires <= hi_time)
			hi_time = leapexpires - 1;
	}
}

/* A在C语言环境中是空格字符吗？ */
static bool fc_is_space(char fc_a)
{
	switch (fc_a)
	{
		default:
			return false;
		case ' ':
		case '\f':
		case '\n':
		case '\r':
		case '\t':
		case '\v':
			return true;
	}
}

/* A在C语言环境中是字母字符吗？ */
static bool fc_is_alpha(char fc_a)
{
	switch (fc_a)
	{
		default:
			return false;
		case 'A':
		case 'B':
		case 'C':
		case 'D':
		case 'E':
		case 'F':
		case 'G':
		case 'H':
		case 'I':
		case 'J':
		case 'K':
		case 'L':
		case 'M':
		case 'N':
		case 'O':
		case 'P':
		case 'Q':
		case 'R':
		case 'S':
		case 'T':
		case 'U':
		case 'V':
		case 'W':
		case 'X':
		case 'Y':
		case 'Z':
		case 'a':
		case 'b':
		case 'c':
		case 'd':
		case 'e':
		case 'f':
		case 'g':
		case 'h':
		case 'i':
		case 'j':
		case 'k':
		case 'l':
		case 'm':
		case 'n':
		case 'o':
		case 'p':
		case 'q':
		case 'r':
		case 's':
		case 't':
		case 'u':
		case 'v':
		case 'w':
		case 'x':
		case 'y':
		case 'z':
			return true;
	}
}

/* 如果 A 是 C 语言环境中的一个大写字符，则返回它的小写对应字符。否则，返回 A。 */
static char fc_lowerit(char fc_a)
{
	switch (fc_a)
	{
		default:
			return fc_a;
		case 'A':
			return 'a';
		case 'B':
			return 'b';
		case 'C':
			return 'c';
		case 'D':
			return 'd';
		case 'E':
			return 'e';
		case 'F':
			return 'f';
		case 'G':
			return 'g';
		case 'H':
			return 'h';
		case 'I':
			return 'i';
		case 'J':
			return 'j';
		case 'K':
			return 'k';
		case 'L':
			return 'l';
		case 'M':
			return 'm';
		case 'N':
			return 'n';
		case 'O':
			return 'o';
		case 'P':
			return 'p';
		case 'Q':
			return 'q';
		case 'R':
			return 'r';
		case 'S':
			return 's';
		case 'T':
			return 't';
		case 'U':
			return 'u';
		case 'V':
			return 'v';
		case 'W':
			return 'w';
		case 'X':
			return 'x';
		case 'Y':
			return 'y';
		case 'Z':
			return 'z';
	}
}

/* 不区分大小写的相等性 */
static bool fc_ciequal(const char *fc_ap, const char *fc_bp)
{
	while (fc_lowerit(*fc_ap) == fc_lowerit(*fc_bp++))
		if (*fc_ap++ == '\0')
			return true;
	return false;
}

static bool fc_itsabbr(const char *fc_abbr, const char *fc_word)
{
	if (fc_lowerit(*fc_abbr) != fc_lowerit(*fc_word))
		return false;
	++fc_word;
	while (*++fc_abbr != '\0')
		do
		{
			if (*fc_word == '\0')
				return false;
		} while (fc_lowerit(*fc_word++) != fc_lowerit(*fc_abbr));
	return true;
}

/* 如果 ABBR 是 WORD 的初始前缀，忽略 ASCII 大小写，则返回 true。 */

static bool fc_ciprefix(char const *fc_abbr, char const *fc_word)
{
	do
		if (!*fc_abbr)
			return true;
	while (fc_lowerit(*fc_abbr++) == fc_lowerit(*fc_word++));

	return false;
}

static const struct lookup *
fc_byword(const char *fc_word, const struct lookup *fc_table)
{
	const struct lookup *fc_foundlp;
	const struct lookup *fc_lp;

	if (fc_word == NULL || fc_table == NULL)
		return NULL;

	/*
	 * 如果 TABLE 是 LASTS 并且单词以 "last" 开头且后面跟着非 '-' 字符，则跳过 "last" 并在 WDAY_NAMES 中查找。警告任何使用未记录前缀 "last-" 的情况。
	 */
	if (fc_table == lasts && fc_ciprefix("last", fc_word) && fc_word[4])
	{
		if (fc_word[4] == '-')
			fc_warning(_("\"%s\" is undocumented; use \"last%s\" instead"),
					fc_word, fc_word + 5);
		else
		{
			fc_word += 4;
			fc_table = wday_names;
		}
	}

	/*
	 * 查找精确匹配。
	 */
	for (fc_lp = fc_table; fc_lp->l_word != NULL; ++fc_lp)
		if (fc_ciequal(fc_word, fc_lp->l_word))
			return fc_lp;

	/*
	 * 查找不精确匹配。
	 */
	fc_foundlp = NULL;
	for (fc_lp = fc_table; fc_lp->l_word != NULL; ++fc_lp)
		if (fc_ciprefix(fc_word, fc_lp->l_word))
		{
			if (fc_foundlp == NULL)
				fc_foundlp = fc_lp;
			else
				return NULL;	/* 多个不精确匹配 */
		}

	if (fc_foundlp && noise)
	{
		/* 警告有关与 2017c zic 之前版本的向后兼容性问题。 */
		bool		fc_pre_2017c_match = false;

		for (fc_lp = fc_table; fc_lp->l_word; fc_lp++)
			if (fc_itsabbr(fc_word, fc_lp->l_word))
			{
				if (fc_pre_2017c_match)
				{
					fc_warning(_("\"%s\" is ambiguous in pre-2017c zic"), fc_word);
					break;
				}
				fc_pre_2017c_match = true;
			}
	}

	return fc_foundlp;
}

static char ** fc_getfields(char *fc_cp)
{
	char	   *fc_dp;
	char	  **fc_array;
	int			fc_nsubs;

	if (fc_cp == NULL)
		return NULL;
	fc_array = fc_emalloc(fc_size_product(strlen(fc_cp) + 1, sizeof *fc_array));
	fc_nsubs = 0;
	for (;;)
	{
		while (fc_is_space(*fc_cp))
			++fc_cp;
		if (*fc_cp == '\0' || *fc_cp == '#')
			break;
		fc_array[fc_nsubs++] = fc_dp = fc_cp;
		do
		{
			if ((*fc_dp = *fc_cp++) != '"')
				++fc_dp;
			else
				while ((*fc_dp = *fc_cp++) != '"')
					if (*fc_dp != '\0')
						++fc_dp;
					else
					{
						fc_error(_("Odd number of quotation marks"));
						exit(EXIT_FAILURE);
					}
		} while (*fc_cp && *fc_cp != '#' && !fc_is_space(*fc_cp));
		if (fc_is_space(*fc_cp))
			++fc_cp;
		*fc_dp = '\0';
	}
	fc_array[fc_nsubs] = NULL;
	return fc_array;
}

static void fc_time_overflow(void)
{
	fc_error(_("time overflow"));
	exit(EXIT_FAILURE);
}

static zic_t fc_oadd(zic_t fc_t1, zic_t fc_t2)
{
	if (fc_t1 < 0 ? fc_t2 < ZIC_MIN - fc_t1 : ZIC_MAX - fc_t1 < fc_t2)
		fc_time_overflow();
	return fc_t1 + fc_t2;
}

static zic_t fc_tadd(zic_t fc_t1, zic_t fc_t2)
{
	if (fc_t1 < 0)
	{
		if (fc_t2 < min_time - fc_t1)
		{
			if (fc_t1 != min_time)
				fc_time_overflow();
			return min_time;
		}
	}
	else
	{
		if (max_time - fc_t1 < fc_t2)
		{
			if (fc_t1 != max_time)
				fc_time_overflow();
			return max_time;
		}
	}
	return fc_t1 + fc_t2;
}

/*
 * 给定一个规则和一个年份，计算该年规则所指日期（自 1970 年 1 月 1 日起的秒数，00:00 本地时间）。
 */

static zic_t fc_rpytime(const struct rule *fc_rp, zic_t fc_wantedy)
{
	int			fc_m,
				fc_i;
	zic_t		fc_dayoff;			/* 向 Margaret O. 致敬 */
	zic_t		fc_t,
				fc_y;

	if (fc_wantedy == ZIC_MIN)
		return min_time;
	if (fc_wantedy == ZIC_MAX)
		return max_time;
	fc_dayoff = 0;
	fc_m = TM_JANUARY;
	fc_y = EPOCH_YEAR;
	if (fc_y < fc_wantedy)
	{
		fc_wantedy -= fc_y;
		fc_dayoff = (fc_wantedy / YEARSPERREPEAT) * (SECSPERREPEAT / SECSPERDAY);
		fc_wantedy %= YEARSPERREPEAT;
		fc_wantedy += fc_y;
	}
	else if (fc_wantedy < 0)
	{
		fc_dayoff = (fc_wantedy / YEARSPERREPEAT) * (SECSPERREPEAT / SECSPERDAY);
		fc_wantedy %= YEARSPERREPEAT;
	}
	while (fc_wantedy != fc_y)
	{
		if (fc_wantedy > fc_y)
		{
			fc_i = len_years[isleap(fc_y)];
			++fc_y;
		}
		else
		{
			--fc_y;
			fc_i = -len_years[isleap(fc_y)];
		}
		fc_dayoff = fc_oadd(fc_dayoff, fc_i);
	}
	while (fc_m != fc_rp->r_month)
	{
		fc_i = len_months[isleap(fc_y)][fc_m];
		fc_dayoff = fc_oadd(fc_dayoff, fc_i);
		++fc_m;
	}
	fc_i = fc_rp->r_dayofmonth;
	if (fc_m == TM_FEBRUARY && fc_i == 29 && !isleap(fc_y))
	{
		if (fc_rp->r_dycode == DC_DOWLEQ)
			--fc_i;
		else
		{
			fc_error(_("use of 2/29 in non leap-year"));
			exit(EXIT_FAILURE);
		}
	}
	--fc_i;
	fc_dayoff = fc_oadd(fc_dayoff, fc_i);
	if (fc_rp->r_dycode == DC_DOWGEQ || fc_rp->r_dycode == DC_DOWLEQ)
	{
		zic_t		fc_wday;

#define LDAYSPERWEEK	((zic_t) DAYSPERWEEK)
		fc_wday = EPOCH_WDAY;

		/*
		 * 不要信任负数的取模运算。
		 */
		if (fc_dayoff >= 0)
			fc_wday = (fc_wday + fc_dayoff) % LDAYSPERWEEK;
		else
		{
			fc_wday -= ((-fc_dayoff) % LDAYSPERWEEK);
			if (fc_wday < 0)
				fc_wday += LDAYSPERWEEK;
		}
		while (fc_wday != fc_rp->r_wday)
			if (fc_rp->r_dycode == DC_DOWGEQ)
			{
				fc_dayoff = fc_oadd(fc_dayoff, 1);
				if (++fc_wday >= LDAYSPERWEEK)
					fc_wday = 0;
				++fc_i;
			}
			else
			{
				fc_dayoff = fc_oadd(fc_dayoff, -1);
				if (--fc_wday < 0)
					fc_wday = LDAYSPERWEEK - 1;
				--fc_i;
			}
		if (fc_i < 0 || fc_i >= len_months[isleap(fc_y)][fc_m])
		{
			if (noise)
				fc_warning(_("rule goes past start/end of month; \
will not work with pre-2004 versions of zic"));
		}
	}
	if (fc_dayoff < min_time / SECSPERDAY)
		return min_time;
	if (fc_dayoff > max_time / SECSPERDAY)
		return max_time;
	fc_t = (zic_t) fc_dayoff * SECSPERDAY;
	return fc_tadd(fc_t, fc_rp->r_tod);
}

static void fc_newabbr(const char *fc_string)
{
	int			fc_i;

	if (strcmp(fc_string, GRANDPARENTED) != 0)
	{
		const char *fc_cp;
		const char *fc_mp;

		fc_cp = fc_string;
		fc_mp = NULL;
		while (fc_is_alpha(*fc_cp) || ('0' <= *fc_cp && *fc_cp <= '9')
			   || *fc_cp == '-' || *fc_cp == '+')
			++fc_cp;
		if (noise && fc_cp - fc_string < 3)
			fc_mp = _("time zone abbreviation has fewer than 3 characters");
		if (fc_cp - fc_string > ZIC_MAX_ABBR_LEN_WO_WARN)
			fc_mp = _("time zone abbreviation has too many characters");
		if (*fc_cp != '\0')
			fc_mp = _("time zone abbreviation differs from POSIX standard");
		if (fc_mp != NULL)
			fc_warning("%s (%s)", fc_mp, fc_string);
	}
	fc_i = strlen(fc_string) + 1;
	if (charcnt + fc_i > TZ_MAX_CHARS)
	{
		fc_error(_("too many, or too long, time zone abbreviations"));
		exit(EXIT_FAILURE);
	}
	strcpy(&chars[charcnt], fc_string);
	charcnt += fc_i;
}

/* 确保 ARGNAME 的目录存在，通过创建任何缺失的目录。如果是 ANCESTORS，则仅为 ARGNAME 的祖先创建；否则，也为 ARGNAME 创建。如果遇到问题，则以失败退出。不要将现存的非目录视为问题。 */
static void fc_mkdirs(char const *fc_argname, bool fc_ancestors)
{
	char	   *fc_name;
	char	   *fc_cp;

	fc_cp = fc_name = fc_ecpyalloc(fc_argname);

	/*
	 * 在 MS-Windows 系统上，不用担心驱动器字母或反斜杠，因为在实际操作中这应该足够。时区名称不使用驱动器字母和反斜杠。如果 zic 的 -d 选项没有命名已存在的目录，则可以使用斜杠将已存在的祖先前缀与将要创建的子目录分开。
	 */

	/* 不要创建根目录，因为它必须存在。 */
	while (*fc_cp == '/')
		fc_cp++;

	while (fc_cp && ((fc_cp = strchr(fc_cp, '/')) || !fc_ancestors))
	{
		if (fc_cp)
			*fc_cp = '\0';

		/*
		 * 尝试创建它。如果创建失败（因为目录已存在），那也没关系，可能是因为其他进程刚刚创建了它。为了简化，不先检查它是否已经存在，因为如果 mkdir 失败时仍会进行检查。
		 */
		if (mkdir(fc_name, MKDIR_UMASK) != 0)
		{
			/*
			 * 为了速度，如果 errno == EEXIST，则跳过其目录检查。由于 mkdirs 仅在子文件的打开操作因 ENOENT 失败后调用，因此 EEXIST 在这里意味着其目录存在。
			 */
			int			fc_err = errno;

			if (fc_err != EEXIST && !fc_itsdir(fc_name))
			{
				fc_error(_("%s: Cannot create directory %s: %s"),
					  progname, fc_name, strerror(fc_err));
				exit(EXIT_FAILURE);
			}
		}
		if (fc_cp)
			*fc_cp++ = '/';
	}
	free(fc_name);
}


#ifdef WIN32
/*
 * 在 win32 上运行
 */
int link(const char *oldpath, const char *newpath)
{
	if (!CopyFile(oldpath, newpath, false))
	{
		_dosmaperr(GetLastError());
		return -1;
	}
	return 0;
}
#endif
